# Request question research

Source: https://docs.musicnerd.net/api-reference/research/question

Request question-directed research

### Broader follow-ups and recoverable assessment errors

Clients can provide `excludeSourceUrls` (at most 10 distinct HTTP(S) URLs, each at most 2,000 characters) when a listener asks for other activity. These are exact public citation URLs already discussed, not private conversation or instructions. Exclusions are part of the request identity and apply before saved-evidence selection; they do not authorize new providers or bypass source/date constraints. A broader recent-activity follow-up uses `retrieval: "latest"` without anchoring itself to the previous single original.

If a saved-evidence assessment returns an invalid structured model response, the durable worker allows one additional assessment on a later slice, rereading current originals. The retry is recorded and counted: saved-only work permits at most two model calls, ordinary work at most three. It does not search or scrape as part of the retry. Network failures, timeouts and unknown execution outcomes are not automatically retried. A second invalid result remains a failed operation, not a finding that evidence does not exist.

## POST /api/artist/{id}/research/questions

Full OpenAPI specification: https://docs.musicnerd.net/spec/question-research.json

## Authentication

This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification.

[Authentication guide](https://docs.musicnerd.net/authentication)

## Operation and referenced schemas

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "Question-directed artist research",
    "version": "0.1.0",
    "description": "Preview contract. Apply the Web-owned research schema before enabling the matching API implementation. Web Ask About reserves a bounded planning slot before its model call: at most 10 new question plans per artist and 200 globally over rolling 24 hours, recorded without question or conversation text. This is separate from the API research ledgers; polling existing jobs does not plan again. Rejected reservations perform no model call. Planning uses no automatic model retries."
  },
  "servers": [
    {
      "url": "https://musicnerd-api.vercel.app"
    }
  ],
  "paths": {
    "/api/artist/{id}/research/questions": {
      "post": {
        "summary": "Request question-directed research",
        "description": "Enqueue or reuse bounded public-evidence research. Checks saved originals first. Explicit URL and date constraints can select stored originals even without matching words in their body. No scraping in enqueue. Never approves Lore or attaches a profile. Newest-available retrieval first checks stored dated public evidence, including own social captions and responses already published in Latest. Stored Spotify/Deezer release catalogs and InProcess moments share durable snapshots with the public Latest endpoint. Provider refreshes run only in queued latest_refresh work; question/status/original reads never refresh these providers. A bounded agent explores the saved public snapshot with paginated browsing, model-chosen searches and exact source-window reads before selecting unchanged evidence; exact supporting quotations and factual claims are checked during answer drafting. If stored evidence is insufficient, the model chooses a next lookup from server-validated web, page or connected social options based on the missing information. There is no fixed social-platform priority. Explicit URL/platform restrictions, account identity and existing provider budgets still constrain execution; the chosen plan is persisted and revalidated before subsequent job slices. Web pages retain explicit publication metadata when unambiguous; unknown dates remain unknown, never replaced by fetch time. Every new job reserves one slot in the rolling global saved-question admission cap (100 per 24 hours), with no per-artist saved-chat cap. This is an operational request limit, not a dollar spending guarantee. Direct or saved-source answers consume no outside-research slot. Only when insufficient saved evidence leads to a validated external lookup does the server reserve outside allowance (5 per artist and 100 globally per rolling 24 hours). That reservation is atomic, lease-fenced, and idempotent for the same job; all existing per-job provider/model/lifetime limits still apply. Exhausted outside allowance ends the job unresolved without starting a provider, with outsideResearchReason=quota plus quotaScope, retryAfterSeconds and resetAt. Exhausted global saved admission returns saved_evidence_quota (429), quotaScope=saved_global, and the same reset fields; Retry-After matches retryAfterSeconds. Reset timing comes from the actual rolling-window boundary and is an estimate assuming no newer reservations consume capacity. Old jobs retain their original quota accounting until their rolling window expires. Single-active-job research_busy remains distinct. Completed matching jobs can be reused independently of these admission limits, subject to current eligibility and cache lifetime. Cache identity preserves topic and all source/date/evidence constraints; reuse is not restricted to an arbitrary last-ten-jobs window. Saved research reads all eligible originals within the existing corpus guard, independently of the Latest UI card limits. Published answers retain publication eligibility and private answers remain excluded. Partial saved support is revalidated and combined with newly collected originals before final assessment. Saved selection returns at most 12,000 original characters; combined outside assessment can return at most six windows totaling 24,000 characters. Exploration uses a current public-research model through AI Gateway, with per-step reservation and usage tracking; external provider budgets are unchanged. Service callers may opt into preferDirectEvidence: true to offer bounded saved candidates before model-driven evidence exploration. This option is not part of the persisted neutral request. Selection candidates means evidence is ready for a checked answer attempt, not that it answers the question. At most six exact windows and 12,000 characters are selected. Latest reporting overviews select the six newest distinct canonical posts first, preferring written evidence over a duplicate transcript, then interleave providers only within that cohort. For overview follow-ups with excluded source URLs and no target URL, known In Process posts from the same connected account and stable collection URL contribute their newest eligible representative before the six-candidate limit. Other social posts remain separate activities. Collection names and question keywords do not determine grouping. This diversifies candidate evidence without treating an entire collection as excluded or answered; focused requests and overviews without exclusions keep individual-post selection. Explicit source, platform, evidence-kind, date and URL exclusions still apply. Semantic project exclusions remain an answer-layer obligation. An empty selection enters ordinary research. Candidate jobs reserve the same quota once and preserve normal claim and expiry checks. Legacy callers never reuse a candidate-only job. A client must register and verify its answer, reread current evidence, and continue the same job if the candidate answer is insufficient or fails verification.",
        "security": [
          {
            "PrivyAuth": []
          },
          {
            "ResearchService": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Persisted acknowledgement",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid authentication",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Current claimant/admin required for private operations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unavailable artist, job or evidence",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "State or revision changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Evidence exceeds supported bounds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Research quota or active-work limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "For a quota response, matches retryAfterSeconds from its actual rolling-window boundary. Active-work conflicts retain a short retry hint.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "503": {
            "description": "Storage, credential or provider unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Request"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Status": {
        "type": "object",
        "required": [
          "status",
          "jobId",
          "stage",
          "message",
          "updatedAt",
          "references",
          "limitations"
        ],
        "properties": {
          "status": {
            "const": "ok"
          },
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "stage": {
            "type": "string",
            "enum": [
              "checking_saved",
              "searching",
              "reading",
              "transcribing",
              "waiting_provider",
              "complete",
              "unresolved",
              "failed",
              "cancelled"
            ]
          },
          "message": {
            "type": "string"
          },
          "provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "reused": {
            "type": "boolean"
          },
          "references": {
            "type": "array",
            "maxItems": 6,
            "items": {
              "$ref": "#/components/schemas/Reference"
            }
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "outsideResearchReason": {
            "type": "string",
            "enum": [
              "quota"
            ],
            "description": "Outside collection was not admitted. Saved sources may still provide a bounded answer. New quota-blocked jobs include a shared quota scope and reset time; retry from a new question after reset, without restarting this terminal job. Older saved-only jobs can omit reset metadata."
          },
          "selection": {
            "type": "string",
            "enum": [
              "candidates",
              "research"
            ],
            "description": "Optional for legacy jobs. candidates is an unassessed evidence selection, not an answer or a sufficiency finding. If the checked answer is insufficient, continue this same job. research means the normal bounded research path."
          },
          "quotaScope": {
            "type": "string",
            "enum": [
              "saved_global",
              "outside_artist",
              "outside_global"
            ],
            "description": "Shared operational budget that blocked this work, never a personal visitor limit."
          },
          "retryAfterSeconds": {
            "type": "integer",
            "minimum": 0,
            "description": "Seconds until resetAt, rounded up. A current status read recomputes this duration; new traffic can consume later capacity."
          },
          "resetAt": {
            "type": "string",
            "format": "date-time",
            "description": "Actual rolling-window boundary calculated when the quota was checked."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "status",
          "error",
          "code"
        ],
        "properties": {
          "status": {
            "const": "error"
          },
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "quotaScope": {
            "type": "string",
            "enum": [
              "saved_global",
              "outside_artist",
              "outside_global"
            ],
            "description": "Shared operational budget that blocked this work, never a personal visitor limit."
          },
          "retryAfterSeconds": {
            "type": "integer",
            "minimum": 0,
            "description": "Seconds until resetAt, rounded up. A current status read recomputes this duration; new traffic can consume later capacity."
          },
          "resetAt": {
            "type": "string",
            "format": "date-time",
            "description": "Actual rolling-window boundary calculated when the quota was checked."
          }
        }
      },
      "Request": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "topic",
          "evidenceNeed"
        ],
        "properties": {
          "topic": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160,
            "description": "Neutral public work/topic terms, not private visitor chat."
          },
          "evidenceNeed": {
            "type": "string",
            "enum": [
              "reporting",
              "release_date",
              "credits",
              "social_caption",
              "spoken_content"
            ]
          },
          "freshness": {
            "type": "string",
            "enum": [
              "stored",
              "recent"
            ],
            "default": "stored",
            "description": "For relevance retrieval, recent defaults to the past seven days unless fromDate is supplied. For latest retrieval, newest available evidence may be older; answers must preserve its actual date and coverage limitations."
          },
          "targetUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048
          },
          "platform": {
            "type": "string",
            "enum": [
              "instagram",
              "tiktok",
              "x",
              "inprocess",
              "spotify",
              "deezer"
            ],
            "description": "Restrict evidence to this platform, including when targetUrl is supplied. InProcess and catalog providers use stored Latest snapshots. If these cannot answer, research ends unresolved with provider_latest_refresh_required; no unrelated social provider is selected. Conflicting platform and target URL ends unresolved with source_platform_mismatch."
          },
          "fromDate": {
            "type": "string",
            "format": "date"
          },
          "toDate": {
            "type": "string",
            "format": "date"
          },
          "retrieval": {
            "type": "string",
            "enum": [
              "relevance",
              "latest"
            ],
            "default": "relevance",
            "description": "Use latest for a newest-available activity overview or newest post on a requested platform. Selects dated originals in descending order without requiring topic keyword overlap or imposing a seven-day cutoff. Explicit fromDate/toDate bounds still apply. Use relevance for a specific topic or fact; latest is not proof of current exhaustive coverage. For latest release_date requests, prefer eligible catalog originals with activityDateKind=release after applying explicit source and date constraints. If none exist, other eligible originals may be read as candidates; their publication dates never establish release dates."
          },
          "excludeSourceUrls": {
            "type": "array",
            "maxItems": 10,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2000,
              "pattern": "^https?://"
            },
            "description": "Exact public citation URLs already discussed; excluded before evidence selection and included in request identity."
          },
          "answerScope": {
            "type": "string",
            "enum": [
              "focused",
              "overview"
            ],
            "default": "focused",
            "description": "Use overview for a broad account of recent activity, focused for a specific post, release or detail. The agent can browse all eligible saved public sources by page or provider, search with its own terms, and read exact original windows. Broad browsing includes multiple available providers instead of selecting only the newest posts from one busy account. Source, date, URL, speech and exclusion constraints remain authoritative. Exploration is bounded by five model steps, twelve tool calls, 24,000 original-text characters, and the job slice deadline. Saved exploration returns at most six read windows and 12,000 characters; an overview selects up to three distinct useful activities. A limit or invalid selection is an operational failure, not evidence that information is absent. Outside collection keeps its existing budgets and can run only from a validated server plan. Saved exploration does not refresh providers. This is not a comprehensive freshness guarantee."
          },
          "preferDirectEvidence": {
            "type": "boolean",
            "description": "Service credential only. Offer bounded candidates for a checked answer attempt before normal research. Optional and false by default; not persisted in the neutral request or its identity."
          }
        }
      },
      "Reference": {
        "type": "object",
        "required": [
          "sourceId",
          "revision",
          "start",
          "end",
          "text",
          "url",
          "curation"
        ],
        "properties": {
          "sourceId": {
            "type": "string",
            "description": "Stable current source reference, including latest:<spotify|deezer|inprocess>:<64hex> provider snapshot originals. Removing/changing the connected account or successfully refreshing away a hidden/removed item makes its old reference unavailable; changed originals have a different revision."
          },
          "revision": {
            "type": "string"
          },
          "start": {
            "type": "integer",
            "minimum": 0
          },
          "end": {
            "type": "integer",
            "minimum": 0
          },
          "text": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "curation": {
            "type": "string",
            "enum": [
              "approved",
              "pending"
            ]
          },
          "evidenceKind": {
            "type": "string",
            "enum": [
              "original_text",
              "caption",
              "provider_transcript"
            ]
          },
          "speaker": {
            "type": "string",
            "enum": [
              "not_applicable",
              "unverified"
            ]
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "retrievedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "truncated": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "activityDate": {
            "type": "string",
            "description": "Optional provider activity date. Preserves YYYY, YYYY-MM or full date/time precision. Never substitutes for source publication time."
          },
          "activityDateKind": {
            "type": "string",
            "enum": [
              "release",
              "moment"
            ],
            "description": "Meaning of activityDate. Exact catalog release-date claims require original catalog text, not publishedAt."
          }
        }
      }
    },
    "securitySchemes": {
      "PrivyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Current artist claimant or administrator Privy access token."
      },
      "ResearchService": {
        "type": "apiKey",
        "in": "header",
        "name": "X-MusicNerd-Research-Key",
        "description": "Server-only MUSICNERD_RESEARCH_API_KEY. Public evidence research only; never grants review or interview-history access."
      }
    }
  }
}
```
