{"openapi":"3.0.3","info":{"title":"Blee Partner API","version":"1.0.0","description":"Submit advertising assets for compliance review, track review status, and manage files."},"servers":[{"url":"https://api.blee.com","description":"Blee API"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT issued by POST /v1/auth/token (M2M) or POST /v1/oauth/token (user-delegated)."}},"schemas":{"AssetResult":{"type":"object","properties":{"summary":{"type":"string","nullable":true,"description":"Human-readable review outcome (e.g. 'Asset approved.' or 'Asset rejected: required disclaimer is missing.'). null while analysis is in progress.","example":"Asset approved."},"analysis_completed_at":{"type":"string","nullable":true,"description":"ISO-8601 timestamp when AI analysis finished. null while still in progress.","example":"2026-05-18T23:00:00Z"}},"required":["summary","analysis_completed_at"],"additionalProperties":{"nullable":true}},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string","nullable":true}},"required":["code","message","param"]}},"required":["error"]},"FileRegistrationResponse":{"type":"object","properties":{"file_id":{"type":"string","description":"Unique ID for this file registration. Pass to submit_bundle."},"signed_url":{"type":"string","description":"Pre-signed URL. PUT your file bytes here (Content-Type header required)."},"expires_at":{"type":"string","description":"ISO-8601 datetime when the signed URL expires."}},"required":["file_id","signed_url","expires_at"]},"AssetReviewer":{"type":"object","properties":{"name":{"type":"string","nullable":true,"description":"Display name of the reviewer (from the user's profile). null when the user has no name set.","example":"Jane Reviewer"},"review_status":{"type":"string","description":"Reviewer's current decision. Internal enum value (e.g. 'NEEDS_REVIEW', 'APPROVED', 'NOT_APPROVED', 'INACTIVE'). Use `review_status_label` for human-readable text when present.","example":"APPROVED"},"review_status_label":{"type":"string","nullable":true,"description":"Company-configured human-readable label for `review_status` when one is set; otherwise null.","example":"Approved with conditions"},"review_status_reason":{"type":"string","nullable":true,"description":"Free-text reason / feedback the reviewer entered.","example":"Looks good once disclaimer is fixed."},"review_status_updated_at":{"type":"string","nullable":true,"description":"ISO-8601 timestamp the reviewer last changed status. null when the reviewer has not acted yet.","example":"2026-05-19T15:42:00Z"}},"required":["name","review_status","review_status_label","review_status_reason","review_status_updated_at"]},"AssetComment":{"type":"object","properties":{"id":{"type":"string","description":"Stable id for the comment."},"comment":{"type":"string","description":"The comment text."},"created_at":{"type":"string","description":"ISO-8601 timestamp the comment was posted.","example":"2026-05-19T15:30:00Z"},"created_by_name":{"type":"string","nullable":true,"description":"Display name of the comment author. null when the author has no name set.","example":"Jane Reviewer"}},"required":["id","comment","created_at","created_by_name"]},"BundleAssetStatus":{"type":"object","properties":{"file_id":{"type":"string"},"filename":{"type":"string"},"media_source_id":{"type":"string"},"dashboard_url":{"type":"string"},"status":{"type":"string","enum":["queued","analyzing","failed","pending_review","approved","not_approved"],"description":"Per-asset lifecycle combining AI-analysis progress and reviewer verdict. `queued` is only returned on PATCH for newly added assets. `analyzing` means AI analysis is still running. `failed` means the AI pipeline could not analyze this asset. `pending_review` means AI analysis completed but no reviewer has reached a verdict yet. `approved` / `not_approved` reflect the reviewer's verdict."},"result":{"allOf":[{"$ref":"#/components/schemas/AssetResult"},{"nullable":true}]},"reviewers":{"type":"array","items":{"$ref":"#/components/schemas/AssetReviewer"},"description":"Per-reviewer status for this asset. Always present (empty array when no reviewers are assigned). Populated regardless of analysis `status` since reviewers can act before AI analysis completes."},"comments":{"type":"array","items":{"$ref":"#/components/schemas/AssetComment"},"description":"Comment thread on this asset, oldest first. Soft-deleted comments are excluded. Always present (empty array when there are no comments)."},"error":{"type":"string","nullable":true},"poll_interval_seconds":{"type":"integer","description":"Recommended number of seconds to wait before polling again. Respect this interval; do not poll more frequently."}},"required":["file_id","filename","media_source_id","dashboard_url","status","result","reviewers","comments","error","poll_interval_seconds"]},"BundleUrlStatus":{"type":"object","properties":{"url":{"type":"string","description":"The submitted URL."},"media_source_id":{"type":"string"},"dashboard_url":{"type":"string"},"status":{"type":"string","enum":["queued","analyzing","failed","pending_review","approved","not_approved"],"description":"Per-asset lifecycle combining AI-analysis progress and reviewer verdict. `queued` is only returned on PATCH for newly added assets. `analyzing` means AI analysis is still running. `failed` means the AI pipeline could not analyze this asset. `pending_review` means AI analysis completed but no reviewer has reached a verdict yet. `approved` / `not_approved` reflect the reviewer's verdict."},"result":{"allOf":[{"$ref":"#/components/schemas/AssetResult"},{"nullable":true}]},"reviewers":{"type":"array","items":{"$ref":"#/components/schemas/AssetReviewer"},"description":"Per-reviewer status for this asset. Always present (empty array when no reviewers are assigned). Populated regardless of analysis `status` since reviewers can act before AI analysis completes."},"comments":{"type":"array","items":{"$ref":"#/components/schemas/AssetComment"},"description":"Comment thread on this asset, oldest first. Soft-deleted comments are excluded. Always present (empty array when there are no comments)."},"error":{"type":"string","nullable":true},"poll_interval_seconds":{"type":"integer","description":"Recommended number of seconds to wait before polling again. Respect this interval; do not poll more frequently."}},"required":["url","media_source_id","dashboard_url","status","result","reviewers","comments","error","poll_interval_seconds"]},"BundleResponse":{"type":"object","properties":{"bundle_id":{"type":"string"},"name":{"type":"string","nullable":true,"description":"Placeholder field. Bundle names are not persisted, so this is always null on status reads."},"external_ref":{"type":"string","nullable":true,"description":"Partner-supplied bundle reference set once at creation (e.g. an MRF Record ID). Stable across polls and PATCHes; `null` when the bundle was created without one."},"status":{"type":"string","enum":["analyzing","failed","pending_review","approved","not_approved","partial"],"description":"Aggregate bundle lifecycle rolled up from per-asset `status` values. Precedence (first match wins): any in-flight asset → `analyzing`; any `not_approved` → `not_approved` (rejection wins); AI-mixed (some `failed`, some non-failed terminal) → `partial`; all `failed` → `failed`; any `pending_review` → `pending_review`; all `approved` → `approved`."},"dashboard_url":{"type":"string"},"created_at":{"type":"string"},"completed_at":{"type":"string","nullable":true},"files":{"type":"array","items":{"$ref":"#/components/schemas/BundleAssetStatus"},"description":"Status of uploaded file assets in this bundle."},"urls":{"type":"array","items":{"$ref":"#/components/schemas/BundleUrlStatus"},"description":"Status of URL-based assets in this bundle."},"poll_interval_seconds":{"type":"integer","description":"Recommended number of seconds to wait before polling again. Respect this interval; do not poll more frequently."}},"required":["bundle_id","name","external_ref","status","dashboard_url","created_at","completed_at","files","urls","poll_interval_seconds"]},"AssetStatus":{"type":"object","properties":{"asset_id":{"type":"string","description":"The media_source_id of this asset. Use as the path parameter for GET /v1/assets/{asset_id}."},"type":{"type":"string","enum":["file","url"],"description":"\"file\" for uploaded files; \"url\" for submitted web links."},"file_id":{"type":"string","description":"Present when type is file. The file_id from POST /v1/files."},"filename":{"type":"string","description":"Present when type is file."},"url":{"type":"string","description":"Present when type is url."},"media_source_id":{"type":"string"},"dashboard_url":{"type":"string"},"status":{"type":"string","enum":["queued","analyzing","failed","pending_review","approved","not_approved"],"description":"Per-asset lifecycle combining AI-analysis progress and reviewer verdict. `queued` is only returned on PATCH for newly added assets. `analyzing` means AI analysis is still running. `failed` means the AI pipeline could not analyze this asset. `pending_review` means AI analysis completed but no reviewer has reached a verdict yet. `approved` / `not_approved` reflect the reviewer's verdict."},"result":{"allOf":[{"$ref":"#/components/schemas/AssetResult"},{"nullable":true}]},"reviewers":{"type":"array","items":{"$ref":"#/components/schemas/AssetReviewer"},"description":"Per-reviewer status for this asset. Always present (empty array when no reviewers are assigned). Populated regardless of analysis `status` since reviewers can act before AI analysis completes."},"comments":{"type":"array","items":{"$ref":"#/components/schemas/AssetComment"},"description":"Comment thread on this asset, oldest first. Soft-deleted comments are excluded. Always present (empty array when there are no comments)."},"error":{"type":"string","nullable":true},"poll_interval_seconds":{"type":"integer","description":"Recommended number of seconds to wait before polling again. Respect this interval; do not poll more frequently."},"external_ref":{"type":"string","nullable":true,"description":"Partner-supplied bundle reference inherited from the asset's bundle. Set once at bundle creation and inherited by new versions. `null` when the bundle was created without one."}},"required":["asset_id","type","media_source_id","dashboard_url","status","result","reviewers","comments","error","poll_interval_seconds","external_ref"]},"CustomTriggerFieldOption":{"type":"object","properties":{"label":{"type":"string","nullable":true,"description":"Human-readable label configured by the company admin. null if unnamed."},"values":{"type":"array","items":{"type":"string"},"description":"Allowed values. Empty array means no restriction is configured."}},"required":["label","values"]},"CustomFieldOption":{"type":"object","properties":{"values":{"type":"array","items":{"type":"string"},"description":"Allowed string values for this field. Empty array means the field accepts free-form text."},"required":{"type":"boolean","description":"True when the field must be present in API (Partner API) bundle submissions."}},"required":["values","required"]},"OptionsResponse":{"type":"object","properties":{"product_lines":{"type":"array","items":{"type":"string"}},"business_units":{"type":"array","items":{"type":"string"}},"company_departments":{"type":"array","items":{"type":"string"}},"distribution_selection":{"type":"array","items":{"type":"string"},"description":"Static enum values valid for all companies."},"custom_trigger_fields":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/CustomTriggerFieldOption"},"description":"Keyed by custom_trigger_field_1 … custom_trigger_field_10. Always includes all 10 slots; slots with no configuration have label: null and values: [].","example":{"custom_trigger_field_1":{"label":"Campaign Type","values":["Brand","Performance"]},"custom_trigger_field_2":{"label":null,"values":[]}}},"custom_fields":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/CustomFieldOption"},"description":"Company-specific fields for `metadata.custom_fields` in bundle submissions. Keys are the exact field names expected by the API. `values` lists allowed enum options (empty = free-form text). `required` indicates the field must be included in API submissions. Empty object for clients without a configured strategy.","example":{"bettermentUploadType":{"values":["Asset Submission","Promotion","Product UX","Award"],"required":true},"bettermentPurposeOfSubmission":{"values":["New Creative","Material Change","Periodic Review"],"required":false}}}},"required":["product_lines","business_units","company_departments","distribution_selection","custom_trigger_fields","custom_fields"]},"FileRegistrationRequest":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"description":"Name of the file to upload, including extension (e.g. 'ad_creative.pdf')","example":"ad_creative.pdf"},"purpose":{"type":"string","enum":["primary","supporting_doc"],"description":"\"primary\" (default) — main creative asset (uploads bucket). \"supporting_doc\" — evidence attachment such as a script or brief (media_source_evidence/temporary/ bucket). Pass the returned file_id in supporting_doc_file_ids on POST /v1/bundles.","example":"primary"}},"required":["filename"]},"BundleSubmitResponse":{"type":"object","properties":{"bundle_id":{"type":"string"},"name":{"type":"string","nullable":true,"description":"Placeholder field. Bundle names are not persisted; this echoes the submitted name on POST only. GET /v1/bundles/{bundle_id} always returns null."},"external_ref":{"type":"string","nullable":true,"description":"Echo of the partner-supplied `external_ref` from the request body. `null` when none was provided. Round-trips on subsequent GET / PATCH responses."},"status":{"type":"string","enum":["analyzing"]},"dashboard_url":{"type":"string"},"created_at":{"type":"string"},"files":{"type":"array","items":{"type":"object","properties":{"file_id":{"type":"string"},"filename":{"type":"string"},"status":{"type":"string","enum":["queued"]},"media_source_id":{"type":"string"},"dashboard_url":{"type":"string"}},"required":["file_id","filename","status","media_source_id","dashboard_url"]},"description":"File assets accepted and queued for analysis."},"urls":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"status":{"type":"string","enum":["queued"]},"media_source_id":{"type":"string"},"dashboard_url":{"type":"string"}},"required":["url","status","media_source_id","dashboard_url"]},"description":"URL assets accepted and queued for analysis."},"failed_files":{"type":"array","items":{"type":"object","properties":{"file_id":{"type":"string"},"filename":{"type":"string"},"error":{"type":"string","description":"Reason the asset could not be submitted for analysis."}},"required":["file_id","filename","error"]},"description":"File assets that could not be submitted due to a server-side error. The bundle is still created; accepted assets proceed normally. Retry rejected items via PATCH /v1/bundles/{bundle_id}. Empty array when all files were accepted."},"failed_urls":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"error":{"type":"string","description":"Reason the asset could not be submitted for analysis."}},"required":["url","error"]},"description":"URL assets that could not be submitted due to a server-side error. Retry rejected items via PATCH /v1/bundles/{bundle_id}. Empty array when all URLs were accepted."},"poll_interval_seconds":{"type":"integer"}},"required":["bundle_id","name","external_ref","status","dashboard_url","created_at","files","urls","failed_files","failed_urls","poll_interval_seconds"]},"BundleRequest":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for this review bundle. Placeholder field: it is echoed back in the immediate submission response but is not persisted, so GET /v1/bundles/{bundle_id} always returns name: null."},"external_ref":{"type":"string","nullable":true,"maxLength":255,"description":"Optional partner-supplied bundle reference (e.g. an MRF Record ID). Set once at bundle creation — it is persisted with every member of the bundle and echoed back on GET /v1/bundles/{bundle_id}, PATCH responses, and GET /v1/assets/{asset_id}. Round-trip only; not queryable. Trimmed before storage; empty or whitespace-only values are stored as null. PATCH does not accept this field (it is inherited from the existing bundle).","example":"MRF-12345"},"subject":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"],"description":"The user on whose behalf this request is being made. Only respected for M2M (client_credentials) tokens; ignored for user-delegated tokens. The email must belong to a user within the authenticated company. When omitted, the operation is attributed to the API service account. Not available via the MCP server; use the direct REST endpoint with an M2M token.","example":{"email":"user@company.com"}},"file_ids":{"type":"array","items":{"type":"string"},"description":"File IDs returned by POST /v1/files (purpose: primary). At least one of file_ids or urls must be provided.","example":["uuid-1","uuid-2"]},"urls":{"type":"array","items":{"type":"string","format":"uri"},"description":"HTTP/HTTPS URLs to submit for compliance review. Can be combined with file_ids in the same bundle. At least one of file_ids or urls must be provided.","example":["https://www.example.com/ad-landing-page","https://docs.google.com/document/d/..."]},"supporting_doc_file_ids":{"type":"array","items":{"type":"string"},"description":"File IDs of supporting documents to attach to this review (scripts, briefs, legal disclaimers, specs, etc.). Always ask the user: 'Do you have any supporting documents to attach, such as a script, brief, or legal disclaimer?' If yes, call register_file with purpose: supporting_doc for each one, upload the bytes to the signed_url, then include the returned file_ids here. Applied to all assets in the bundle.","example":["uuid-3"]},"run_discrepancy_analysis":{"type":"boolean","description":"Compare every asset in this bundle against one of the attached supporting documents and record the differences as discrepancies. Requires at least one supporting_doc_file_ids entry. Defaults to false. Ask the user: 'Should I check these assets against the supporting document for discrepancies?'","example":true},"discrepancy_doc_file_id":{"type":"string","description":"Which supporting document to compare against — must be one of supporting_doc_file_ids. Required when more than one supporting document is attached and run_discrepancy_analysis is true; with a single supporting document it is inferred. Implies run_discrepancy_analysis: true.","example":"uuid-3"},"metadata":{"type":"object","properties":{"assignee_email":{"type":"string","format":"email","description":"Email of the dashboard user to assign this review to."},"due_date":{"type":"string","description":"ISO-8601 datetime for the requested review completion date.","example":"2026-06-01T00:00:00Z"},"expiration_date":{"type":"string","description":"ISO-8601 datetime after which the asset expires.","example":"2026-12-31T00:00:00Z"},"submission_notes":{"type":"string","description":"Free-text notes visible to reviewers."},"business_units":{"type":"array","items":{"type":"string"},"description":"One or more business units for this submission. Call get_options first to get the allowed values, then present them to the user as a list — multiple selections are allowed."},"product_lines":{"type":"array","items":{"type":"string"},"description":"One or more product lines for this submission. Call get_options first to get the allowed values, then present them to the user as a list — multiple selections are allowed."},"distribution_selection":{"type":"array","items":{"type":"string","enum":["SOCIAL_MEDIA","X","YOUTUBE_ADS","YOUTUBE_SHORTS","INSTAGRAM_POST","INSTAGRAM_REELS","INSTAGRAM_STORY","TIKTOK","FACEBOOK_ADS","LINKEDIN","PINTEREST","DIGITAL_ADS","PODCAST","BLOG","SMS","RADIO","TV","WEBSITE_OR_PRODUCT","PRODUCT_SCREEN","HOME_PAGE","LANDING_PAGE","FAQ_PAGE","OTHER_WEBSITE_PAGES","PHYSICAL","DIRECT_MAIL","RETAIL","BILLBOARD","CATALOG","FLYER","MAGAZINE","EMAIL","FACEBOOK_POST","ADS","ARTICLE_OR_ONE_PAGER","EMAIL_COMMERCIAL","EMAIL_TRANSACTIONAL","EVENTS","JOB_DESCRIPTION","LEGAL_DOCUMENT","OUT_OF_HOME_OR_PRINT_MEDIA","PARTNER_OR_AFFILIATE","SALES_DECK_OR_PRESENTATION","SCRIPT_OR_STORYBOARD","WEBSITE_OR_APP","UX_OR_NOTIFICATIONS","VIDEO_OR_SOUND_CLIP","INSTAGRAM","YOUTUBE","FACEBOOK","WEBSITE","PRODUCT_OR_APP","OTHER","BANNER","POSTER","AUDIO","VIDEO","PWS_OR_APP","POSTER_OR_PRESS_ADVERT","FLYER_BROCHURE","SOCIAL_MEDIA_INFLUENCER","SOCIAL_MEDIA_NON_INFLUENCER","IN_APP","MAILER","ORGANIC_SOCIAL","PAID_SEARCH","PAID_SOCIAL","PRESS_RELEASE","WEB","REDDIT","PAID_ADS","CARD_ART_OR_PLASTIC"]},"description":"Distribution channels where this asset will be used. Call get_options to see the full list. Multiple values can be selected.","example":["SOCIAL_MEDIA","EMAIL"]},"company_departments":{"type":"array","items":{"type":"string"},"description":"One or more company departments for this submission. Call get_options first to get the allowed values, then present them to the user as a list — multiple selections are allowed."},"tags":{"type":"array","items":{"type":"string"},"description":"General-purpose tags. Free-form, multiple values allowed."},"custom_tags_1":{"type":"array","items":{"type":"string"},"description":"Custom tag slot 1. Multiple values allowed."},"custom_tags_2":{"type":"array","items":{"type":"string"},"description":"Custom tag slot 2. Multiple values allowed."},"custom_tags_3":{"type":"array","items":{"type":"string"},"description":"Custom tag slot 3. Multiple values allowed."},"custom_tags_4":{"type":"array","items":{"type":"string"},"description":"Custom tag slot 4. Multiple values allowed."},"custom_tags_5":{"type":"array","items":{"type":"string"},"description":"Custom tag slot 5. Multiple values allowed."},"custom_trigger_field_1":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 1. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_2":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 2. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_3":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 3. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_4":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 4. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_5":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 5. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_6":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 6. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_7":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 7. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_8":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 8. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_9":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 9. Call get_options to see the label and allowed values for this field. Multiple selections allowed."},"custom_trigger_field_10":{"type":"array","items":{"type":"string"},"description":"Custom trigger dimension 10."},"custom_fields":{"type":"object","additionalProperties":{"nullable":true},"description":"Company-specific metadata fields."}},"description":"Metadata and company-specific fields for this submission. Before filling in product_lines, business_units, company_departments, distribution_selection, or custom_trigger_field_1-10, call get_options to retrieve the allowed values and their labels. Present the available options to the user as a numbered or bulleted list and allow them to pick one or more."}}},"AddAssetsToBundleResponse":{"type":"object","properties":{"bundle_id":{"type":"string"},"external_ref":{"type":"string","nullable":true,"description":"Echo of the bundle's partner-supplied `external_ref` (set once at bundle creation; inherited by newly added assets). `null` when the bundle was created without one."},"status":{"type":"string","enum":["analyzing","failed","pending_review","approved","not_approved","partial"],"description":"Updated aggregate bundle status. New assets are queued, so this is typically `analyzing` immediately after PATCH."},"dashboard_url":{"type":"string"},"added_files":{"type":"array","items":{"type":"object","properties":{"file_id":{"type":"string"},"filename":{"type":"string"},"status":{"type":"string","enum":["queued"]},"media_source_id":{"type":"string"},"dashboard_url":{"type":"string"},"version_one_media_source_id":{"type":"string","nullable":true}},"required":["file_id","filename","status","media_source_id","dashboard_url","version_one_media_source_id"]},"description":"The newly-added file assets."},"added_urls":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"status":{"type":"string","enum":["queued"]},"media_source_id":{"type":"string"},"dashboard_url":{"type":"string"},"version_one_media_source_id":{"type":"string","nullable":true}},"required":["url","status","media_source_id","dashboard_url","version_one_media_source_id"]},"description":"The newly-added URL assets."}},"required":["bundle_id","external_ref","status","dashboard_url","added_files","added_urls"]},"AddAssetsToBundleRequest":{"type":"object","properties":{"subject":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"],"description":"The user on whose behalf this request is being made. Only respected for M2M (client_credentials) tokens; ignored for user-delegated tokens. The email must belong to a user within the authenticated company. Not available via the MCP server; use the direct REST endpoint with an M2M token.","example":{"email":"user@company.com"}},"files":{"type":"array","items":{"type":"object","properties":{"file_id":{"type":"string","description":"file_id of the new asset to add (from POST /v1/files)."},"version_one_media_source_id":{"type":"string","description":"media_source_id of the original (v1) asset this file supersedes. When provided, the new asset is created as a new version in the chain."}},"required":["file_id"]},"description":"File assets to append to the bundle. Required when urls is absent."},"urls":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The http or https URL to add."},"version_one_media_source_id":{"type":"string","description":"media_source_id of the original (v1) URL asset this supersedes. When provided, the new asset is created as a new version in the chain."}},"required":["url"]},"description":"URL assets to append to the bundle. Required when files is absent."}}},"VersionAssetRequest":{"anyOf":[{"type":"object","properties":{"subject":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"],"description":"The user on whose behalf this request is being made. Only respected for M2M (client_credentials) tokens; ignored for user-delegated tokens. The email must belong to a user within the authenticated company. Not available via the MCP server; use the direct REST endpoint with an M2M token.","example":{"email":"user@company.com"}},"file_id":{"type":"string","description":"The file_id returned by POST /v1/files for the new version's file."}},"required":["file_id"]},{"type":"object","properties":{"subject":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"],"description":"The user on whose behalf this request is being made. Only respected for M2M (client_credentials) tokens; ignored for user-delegated tokens. The email must belong to a user within the authenticated company. Not available via the MCP server; use the direct REST endpoint with an M2M token.","example":{"email":"user@company.com"}},"url":{"type":"string","format":"uri","description":"The http or https URL of the new version (for URL assets)."}},"required":["url"]}]},"AskAnalyticsResponse":{"type":"object","properties":{"question":{"type":"string","description":"Echo of the question as the agent understood it."},"sql":{"type":"string","nullable":true,"description":"The SELECT statement the agent executed (with COMPANY_ID substituted). Null if no query was run."},"rows":{"type":"array","items":{"type":"object","additionalProperties":{"nullable":true}},"description":"The query result rows as JSON objects."},"row_count":{"type":"integer","description":"Number of rows returned by the underlying SQL query."},"truncated":{"type":"boolean","description":"True when the SQL hit your company's row limit. When true, treat `rows` as a PARTIAL view of the underlying result set — do not present any totals, counts, or completeness claims derived from the returned rows as authoritative. The `analysis` and `summary` fields will also carry an explicit truncation notice in this case."},"analysis":{"type":"string","description":"Plain-English answer with concrete numbers from the result rows. Suitable for direct display to the end user."},"summary":{"type":"string","description":"One-line summary of the answer."},"chart_recommendation":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["bar","line","pie","table"]},"xKey":{"type":"string"},"yKey":{"type":"string"},"title":{"type":"string"}},"required":["type","xKey","yKey","title"],"description":"Optional chart recommendation derived from the result shape; null for single-number or textual answers."},"duration_ms":{"type":"integer","description":"Wall-clock time spent inside the analytics agent."},"disclaimer":{"type":"string","description":"Static AI-generated-analytics disclaimer. Surfaced on every successful response so consumers can render it prominently. The same text is also appended to `analysis` so MCP clients that only display the analysis still show it to the end user."}},"required":["question","sql","rows","row_count","truncated","analysis","summary","chart_recommendation","duration_ms","disclaimer"]},"AskAnalyticsRequest":{"type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":2000,"description":"A natural-language question about your company's compliance data. Examples: 'How many assets were submitted today?', 'Show me violations by rule for the last 30 days', 'Which reviewers have the most open assets?'. Strictly scoped to your company by the bearer token — never include another company's name or ID.","example":"How many assets were submitted today?"},"max_rows":{"type":"integer","minimum":1,"maximum":5000,"description":"Optional row limit for the result set. Your company has a row limit — 50 by default — and any value above it is silently clamped to it, so this can lower the limit for a single call but never raise it. To raise your company's row limit, talk to your customer support specialist. ask_analytics is a Q&A endpoint rather than a bulk-export one, so if you need a complete view of a large result set, ask a narrower question (filter by date, status, owner, etc.) instead."}},"required":["question"]}},"parameters":{}},"paths":{"/v1/options":{"get":{"operationId":"get_options","summary":"Retrieve valid metadata field values for this company","description":"Returns the allowed values for product_lines, business_units, company_departments, distribution_selection, custom_trigger_field_1-10 (with their human-readable labels), and custom_fields (company-specific metadata fields with their allowed values and required flags). Always call this before asking the user to fill in any of those fields. Present the returned values as a numbered or bulleted list and let the user pick one or more. Fields with an empty values array have no restriction — any value is accepted.","tags":["Options"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Valid options for the authenticated company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptionsResponse"}}}},"401":{"description":"Invalid or missing token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/files":{"post":{"operationId":"register_file","summary":"Register a file and obtain a signed upload URL","description":"Step 1 of submission. Returns a `signed_url` — PUT your file bytes there, then pass `file_id` to POST /v1/bundles.","tags":["Files"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileRegistrationRequest"}}}},"responses":{"201":{"description":"File registered. PUT bytes to `signed_url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileRegistrationResponse"}}}},"422":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/bundles":{"post":{"operationId":"submit_bundle","summary":"Submit files and/or URLs for compliance review","description":"Step 2 of submission. Provide `file_ids` (registered via POST /v1/files), `urls` (web links), or both — at least one is required. Workflow: (1) Ask the user if they have supporting documents (scripts, briefs, disclaimers). If yes, register each with register_file using purpose: supporting_doc and include the file_ids in supporting_doc_file_ids. (1a) If supporting documents were attached, ask whether the assets should be checked against one of them for discrepancies. If yes, set run_discrepancy_analysis: true (and discrepancy_doc_file_id when more than one document is attached). (2) Call get_options to get allowed values for product_lines, business_units, company_departments, distribution_selection, and custom_trigger_fields — present them to the user as a list for multi-selection. (3) Submit the bundle. Submissions with values not in the allowed lists are rejected with 422.","tags":["Bundles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleRequest"}}}},"responses":{"202":{"description":"Bundle accepted. `files` and `urls` list accepted assets; `failed_files` and `failed_urls` list any assets that could not be submitted (empty when all assets succeeded).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleSubmitResponse"}}}},"422":{"description":"Validation error (missing fields, invalid custom_fields, etc.) or `idempotency_conflict` when the same `Idempotency-Key` is reused with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/bundles/{bundle_id}":{"get":{"operationId":"get_bundle_status","summary":"Get bundle status","description":"Poll the aggregate status of a bundle. Check each file's `status` field: `queued → analyzing → complete | failed`. The response includes `poll_interval_seconds`; wait at least that many seconds before polling again.","tags":["Bundles"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"The bundle_id returned by POST /v1/bundles."},"required":true,"name":"bundle_id","in":"path"}],"responses":{"200":{"description":"Bundle status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleResponse"}}}},"404":{"description":"Bundle not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"operationId":"add_assets_to_bundle","summary":"Append additional files or URLs to an existing bundle","description":"Appends one or more previously-registered files or web URLs to an existing bundle and queues them for analysis. Supports `Idempotency-Key` header to safely retry without duplicating assets — replaying the same key with the same body returns the original cached response, while reusing the key with a different body returns `422 idempotency_conflict`. Use `version_one_media_source_id` to submit a new version of an existing asset (the version chain is updated automatically). At least one of `files` or `urls` must be provided.","tags":["Bundles"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"The bundle_id to append assets to."},"required":true,"name":"bundle_id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsToBundleRequest"}}}},"responses":{"200":{"description":"Assets appended successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsToBundleResponse"}}}},"404":{"description":"Bundle or file not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"File registration has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error, or `idempotency_conflict` when the same `Idempotency-Key` is reused with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/assets/{asset_id}":{"get":{"operationId":"get_asset_status","summary":"Get status of a single asset","description":"Returns detailed status for a single file or URL asset. `asset_id` is the `media_source_id` returned in both `files[]` and `urls[]` of the bundle response — it is the universal identifier for all asset types. The response includes `poll_interval_seconds`; wait at least that many seconds before polling again.","tags":["Assets"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"The media_source_id of the asset (from the files[] or urls[] array in the bundle response)."},"required":true,"name":"asset_id","in":"path"}],"responses":{"200":{"description":"Asset status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStatus"}}}},"404":{"description":"Asset not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"operationId":"version_asset","summary":"Submit a new version of an existing asset","description":"Creates a new version of the asset identified by `asset_id`. `asset_id` may be the `media_source_id` of **any** version in the chain — the endpoint resolves the version-1 anchor automatically. The new asset is queued for analysis and can be tracked with `GET /v1/assets/{asset_id}` using the `asset_id` returned here. All versioning logic (auto-incrementing version number, flipping `latest_version`) is handled server-side. Provide exactly one of `file_id` (uploaded file) or `url` (web link).","tags":["Assets"],"security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"The media_source_id of any existing version of the asset (returned in files[] or urls[] of the bundle response)."},"required":true,"name":"asset_id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionAssetRequest"}}}},"responses":{"202":{"description":"New version accepted and queued for analysis.","content":{"application/json":{"schema":{"type":"object","properties":{"asset_id":{"type":"string","description":"The media_source_id of the newly created version."},"media_source_id":{"type":"string"},"version_one_media_source_id":{"type":"string","description":"The media_source_id of the first version in this chain."},"status":{"type":"string","enum":["queued"]},"dashboard_url":{"type":"string"},"poll_interval_seconds":{"type":"number"}},"required":["asset_id","media_source_id","version_one_media_source_id","status","dashboard_url","poll_interval_seconds"]}}}},"400":{"description":"Missing or invalid request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Asset or file not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"File has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/token":{"post":{"summary":"Obtain an M2M access token (client credentials)","description":"Exchange a `client_id` and `client_secret` for a short-lived JWT. Pass this as `Authorization: Bearer <token>`.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"grant_type":{"type":"string","enum":["client_credentials"]},"client_id":{"type":"string"},"client_secret":{"type":"string"}},"required":["grant_type","client_id","client_secret"]}}}},"responses":{"200":{"description":"Access token.","content":{"application/json":{"schema":{"type":"object","properties":{"access_token":{"type":"string"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"number"}},"required":["access_token","token_type","expires_in"]}}}},"401":{"description":"Invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}