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

# Bulk-edit prompts

> Edit existing prompts in bulk, in place: the round trip for a spreadsheet of prompts. Read them with `GET /projects/{projectId}/prompts` (each row carries its `id` and `currentRevision`; `?format=csv` downloads them), change what you need, and send up to 500 rows back in `prompts`. Each row names a prompt by `promptId` and gives the end state for any of `text`, `isBranded`, `intentCategory`, `topics` (names; the list replaces the prompt's topics, `[]` removes them, names the project lacks are created) and `locations` (the full set, at most 5; a location left out stops being tracked and its history is kept). Omit a field to leave it alone; a row that changes nothing writes nothing. Send each prompt's `expectedRevision`: a prompt edited since you read it comes back as `conflict` instead of being overwritten. Every row of one prompt must be in the same request. A reworded prompt keeps its id and past answers, and its old wording stays as a revision. A batch whose added locations do not fit the prompt allowance is refused with 403 `quota_exceeded` and nothing written; past that, each prompt stands alone and reports its own `outcome`. Result entries carry each `promptId` trimmed and lower-cased, so match ids case-insensitively. New prompts go through `POST /projects/{projectId}/prompts`. `dryRun: true` returns the same plan without writing. A dry run plans the edit but does not check the prompt allowance; adding locations to tracked prompts can still be refused with `quota_exceeded` when you apply. Requires a connection minted with the `prompt:draft` capability, and your own project role must allow the write (otherwise 403 `forbidden`). A pitch project that is not currently active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. Supports `Idempotency-Key` replay; a reused key with a different body returns 409 `idempotency_conflict`.



## OpenAPI

````yaml /api-reference/openapi.json post /api/mcp/projects/{projectId}/prompts/sync
openapi: 3.1.0
info:
  title: Searchable REST API
  version: 1.0.0
  summary: >-
    Query AI visibility, sources, sentiment, traffic, and content data, and
    trigger audits/reports/sitemap syncs.
  description: >-
    The Searchable REST API (`/api/mcp/*` and
    `/api/v1/projects/{projectId}/audits`) exposes the same data and actions
    available in the Searchable dashboard and MCP server, over plain HTTP with a
    bearer API key. It is a **read-mostly** surface — 75 of its 91 operations
    are GET. The mutating ones are capability-gated (a bare `write` scope does
    not satisfy a capability such as `prompt:draft`), and those listed under
    [Idempotency](/api-reference/introduction#idempotency) support
    `Idempotency-Key` replay.


    See **[Getting started](/api-reference/introduction)** for auth, rate
    limits, idempotency, and pagination conventions, and the **[error
    reference](/api/errors)** for every error `code` this API returns.


    `/api/mcp/*` is a frozen v1 contract — see the [changelog](/changelog) for
    the deprecation policy.
  contact:
    name: Searchable Support
    email: support@searchable.com
    url: https://docs.searchable.com/api-reference/introduction
servers:
  - url: https://app.searchable.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Projects
    description: List and inspect the projects an API key can reach.
  - name: Visibility
    description: >-
      AI visibility score, platform/topic/prompt/location breakdowns, and
      historical trend.
  - name: Share of Voice
    description: Brand vs. competitor mention-share ranking and its daily trend.
  - name: Competitors
    description: >-
      The project's tracked competitor roster — share of voice, sentiment, and
      mention/citation counts.
  - name: Sources
    description: >-
      Domains and URLs cited by AI answers — citation share, trend, and cached
      page content.
  - name: Sentiment
    description: Brand sentiment summary, trend, and head-to-head competitor comparison.
  - name: Query Fanout
    description: The sub-queries AI models generate when answering tracked prompts.
  - name: Traffic
    description: >-
      First-party AI-crawler and AI-referral traffic sourced from the
      tracker/CDN pipeline.
  - name: Shopping
    description: AI shopping/product-carousel visibility.
  - name: Ads
    description: >-
      Sponsored ads surfaced in AI answers — advertisers, individual creatives,
      ad-heavy prompts, and the frequency trend.
  - name: Answers
    description: Raw, per-response AI-answer data for a single prompt.
  - name: GSC
    description: Live Google Search Console performance data for the project's linked site.
  - name: GA4
    description: Live Google Analytics 4 traffic and AI-referral data.
  - name: Audits & Issues
    description: >-
      Page audits (technical + AEO), site issues, monitored pages, and sitemap
      sync.
  - name: Reports
    description: Generate and list shareable visibility/sentiment reports.
  - name: Articles
    description: Content pieces (articles) created in Searchable.
  - name: Opportunities
    description: >-
      Actionable, prioritized suggestions Searchable has surfaced for the
      project.
  - name: Brand
    description: Brand profile (knowledge base + AI-observed facts) and domain authority.
  - name: Prompts
    description: >-
      The project's prompt catalog (tracked, untracked, and AI-suggested
      prompts).
paths:
  /api/mcp/projects/{projectId}/prompts/sync:
    post:
      tags:
        - Prompts
      summary: Bulk-edit prompts
      description: >-
        Edit existing prompts in bulk, in place: the round trip for a
        spreadsheet of prompts. Read them with `GET
        /projects/{projectId}/prompts` (each row carries its `id` and
        `currentRevision`; `?format=csv` downloads them), change what you need,
        and send up to 500 rows back in `prompts`. Each row names a prompt by
        `promptId` and gives the end state for any of `text`, `isBranded`,
        `intentCategory`, `topics` (names; the list replaces the prompt's
        topics, `[]` removes them, names the project lacks are created) and
        `locations` (the full set, at most 5; a location left out stops being
        tracked and its history is kept). Omit a field to leave it alone; a row
        that changes nothing writes nothing. Send each prompt's
        `expectedRevision`: a prompt edited since you read it comes back as
        `conflict` instead of being overwritten. Every row of one prompt must be
        in the same request. A reworded prompt keeps its id and past answers,
        and its old wording stays as a revision. A batch whose added locations
        do not fit the prompt allowance is refused with 403 `quota_exceeded` and
        nothing written; past that, each prompt stands alone and reports its own
        `outcome`. Result entries carry each `promptId` trimmed and lower-cased,
        so match ids case-insensitively. New prompts go through `POST
        /projects/{projectId}/prompts`. `dryRun: true` returns the same plan
        without writing. A dry run plans the edit but does not check the prompt
        allowance; adding locations to tracked prompts can still be refused with
        `quota_exceeded` when you apply. Requires a connection minted with the
        `prompt:draft` capability, and your own project role must allow the
        write (otherwise 403 `forbidden`). A pitch project that is not currently
        active refuses with 409 `INACTIVE_PITCH_PROMPT_MUTATION`. Supports
        `Idempotency-Key` replay; a reused key with a different body returns 409
        `idempotency_conflict`.
      operationId: syncPrompts
      parameters:
        - $ref: '#/components/parameters/projectId'
        - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - prompts
              properties:
                prompts:
                  type: array
                  minItems: 1
                  maxItems: 500
                  items:
                    type: object
                    required:
                      - promptId
                    properties:
                      promptId:
                        type: string
                        description: The prompt's id.
                      expectedRevision:
                        type: integer
                        minimum: 1
                        description: The prompt's `currentRevision` when you read it.
                      text:
                        type: string
                        maxLength: 2000
                        description: >-
                          The complete new wording, 5 to 500 characters after
                          trimming.
                      isBranded:
                        type: boolean
                      intentCategory:
                        type: string
                        enum:
                          - informational
                          - commercial
                          - transactional
                          - navigational
                      topics:
                        type: array
                        maxItems: 50
                        items:
                          type: string
                        description: >-
                          Topic names, at most 10 of up to 100 characters each;
                          replaces the prompt's topics. An empty list removes
                          them all. A list of 11 to 50 names is accepted, but
                          that prompt comes back `invalid`.
                      locations:
                        type: array
                        maxItems: 50
                        description: >-
                          The full location set, at most 5 after merging a
                          prompt's rows. A city that is not recognised is
                          tracked country-wide.
                        items:
                          type: object
                          required:
                            - country
                          properties:
                            country:
                              type: string
                              description: ISO 3166-1 alpha-2 code or country name.
                            city:
                              type: string
                            state:
                              type: string
                dryRun:
                  type: boolean
                  description: Return the plan without writing.
      responses:
        '200':
          description: Each named prompt's outcome.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimitPolicy'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
            X-Idempotent-Replay:
              $ref: '#/components/headers/XIdempotentReplay'
            X-Idempotent-Skipped:
              $ref: '#/components/headers/XIdempotentSkipped'
          content:
            application/json:
              schema:
                type: object
                properties:
                  dryRun:
                    type: boolean
                  counts:
                    type: object
                    properties:
                      changed:
                        type: integer
                      unchanged:
                        type: integer
                      conflict:
                        type: integer
                      not_found:
                        type: integer
                      invalid:
                        type: integer
                      failed:
                        type: integer
                  prompts:
                    type: array
                    items:
                      type: object
                      properties:
                        promptId:
                          type: string
                        outcome:
                          type: string
                          enum:
                            - changed
                            - unchanged
                            - conflict
                            - not_found
                            - invalid
                            - failed
                        text:
                          type: string
                          description: The prompt's wording before this change.
                        changes:
                          type: object
                          description: >-
                            Per changed field (`text`, `isBranded`,
                            `intentCategory`, `topics`, `locations`): `{ before,
                            after }`.
                        currentRevision:
                          type: integer
                          description: >-
                            For `conflict`, the prompt's revision now; for an
                            applied `changed`, the revision it reached.
                        currentText:
                          type: string
                          description: For `conflict`, the prompt's wording now.
                        reason:
                          type: string
                          description: >-
                            For `invalid`, `failed` and `conflict`: why, in a
                            sentence you can act on.
                        notes:
                          type: array
                          items:
                            type: string
                          description: >-
                            Location fallbacks, such as a city that was not
                            recognised and is tracked country-wide.
        '400':
          $ref: '#/components/responses/defaultError'
        '401':
          $ref: '#/components/responses/defaultError'
        '403':
          $ref: '#/components/responses/defaultError'
        '404':
          $ref: '#/components/responses/defaultError'
        '409':
          $ref: '#/components/responses/idempotencyInFlight'
        '429':
          $ref: '#/components/responses/rateLimited'
      security:
        - bearerAuth: []
components:
  parameters:
    projectId:
      name: projectId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: The project id.
    idempotencyKey:
      name: Idempotency-Key
      in: header
      schema:
        type: string
      description: >-
        Optional client-generated key (e.g. a UUID). Retrying with the same key
        within 24h replays the original stored response (`X-Idempotent-Replay:
        true`) instead of repeating the side effect. See
        [Idempotency](/api-reference/introduction#idempotency).
  headers:
    XRequestId:
      description: This request's id. Include it when contacting support.
      schema:
        type: string
        format: uuid
    RateLimit:
      description: >-
        IETF draft rate-limit header:
        `"default";r=<remaining>;t=<seconds-until-reset>`. Omitted on responses
        where the limiter did not run (e.g. a pre-auth validation error, or a
        degraded limiter failing open).
      schema:
        type: string
    RateLimitPolicy:
      description: >-
        IETF draft rate-limit policy: `"default";q=<quota>;w=<window-seconds>`.
        Currently `q=600;w=60`.
      schema:
        type: string
    XRateLimitLimit:
      description: Requests allowed per window (600/minute per key).
      schema:
        type: integer
    XRateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
    XRateLimitReset:
      description: Absolute Unix-epoch second the current window resets.
      schema:
        type: integer
    XIdempotentReplay:
      description: >-
        `true` when this response is a replay of a previously stored successful
        response for the same `Idempotency-Key`.
      schema:
        type: string
        enum:
          - 'true'
    XIdempotentSkipped:
      description: >-
        `body-too-large` when a successful response exceeded the 100KB
        idempotency-store cap and was not cached (the request still ran
        normally).
      schema:
        type: string
        enum:
          - body-too-large
    RetryAfter:
      description: >-
        Seconds to wait before retrying. Set on 429s issued by Searchable's own
        rate limiter and on `409 idempotency_in_flight`; may be absent when an
        upstream provider (e.g. Google) rate-limits.
      schema:
        type: integer
  responses:
    defaultError:
      description: >-
        Error response. `code` is the machine-readable reason — see the [error
        reference](/api/errors) for the full catalog and remediation per code.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        RateLimit:
          $ref: '#/components/headers/RateLimit'
        RateLimit-Policy:
          $ref: '#/components/headers/RateLimitPolicy'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    idempotencyInFlight:
      description: >-
        Another request with the same `Idempotency-Key` is still executing.
        `Retry-After` (5 seconds) is always present — retry with the SAME key
        once it elapses.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        RateLimit:
          $ref: '#/components/headers/RateLimit'
        RateLimit-Policy:
          $ref: '#/components/headers/RateLimitPolicy'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    rateLimited:
      description: >-
        Rate limited. Usually the per-API-key REST limit (600 requests/minute);
        on Google-backed endpoints it can also relay an upstream Google rate
        limit. `Retry-After` (seconds) is present when the limit was applied by
        Searchable's own rate limiter; it may be absent when an upstream
        provider rate-limits.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        RateLimit:
          $ref: '#/components/headers/RateLimit'
        RateLimit-Policy:
          $ref: '#/components/headers/RateLimitPolicy'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  schemas:
    Problem:
      type: object
      description: >-
        RFC 9457 `application/problem+json` error body. `error` is retained
        verbatim as a legacy compat key equal to `message`. Some codes carry
        additional fields beyond the base shape (documented per-code in the
        [error reference](/api/errors)) — always additive, never replacing
        these.
      properties:
        type:
          type: string
          format: uri
          description: >-
            A URL identifying the error kind — the error reference page,
            anchored to `code`.
        title:
          type: string
          description: Human-readable summary of `code`.
        status:
          type: integer
          description: The HTTP status code (matches the response status).
        code:
          type: string
          description: >-
            Machine-readable, stable error code — safe to branch on. See the
            [error reference](/api/errors) for the full catalog.
          enum:
            - unauthorized
            - forbidden
            - missing_scope
            - plan_upgrade_required
            - not_found
            - invalid_argument
            - invalid_country
            - no_domain
            - no_workspace
            - no_pages
            - bulk_limit_exceeded
            - quota_exceeded
            - project_not_runnable
            - pitch_not_supported
            - rate_limited
            - idempotency_in_flight
            - query_timeout
            - gsc_not_connected
            - gsc_site_not_linked
            - gsc_access_denied
            - ga4_not_connected
            - traffic_not_connected
            - internal_error
        error:
          type: string
          description: Legacy compat key. Always equal to `message`.
        message:
          type: string
          description: Human-readable error message (identical to `error`).
        howToFix:
          type: string
          description: Present on actionable errors — a concrete next step.
        requestId:
          type: string
          format: uuid
          description: This request's id. Include it when contacting support.
        requiresUpgrade:
          type: boolean
          description: Present on `plan_upgrade_required` (403).
        retryable:
          type: boolean
          description: >-
            Present on `rate_limited`, `idempotency_in_flight`, and
            `query_timeout`.
        timeout:
          type: boolean
          description: Present on `query_timeout` (504).
        quotaExceeded:
          type: boolean
          description: Present on `quota_exceeded` (403).
        current:
          type: integer
          description: Present on `quota_exceeded` — current usage.
        limit:
          type: integer
          description: Present on `quota_exceeded` — the plan's limit.
        bulkLimitExceeded:
          type: boolean
          description: Present on `bulk_limit_exceeded` (400, `POST /audits`).
        requested:
          type: integer
          description: >-
            Present on `bulk_limit_exceeded` / `quota_exceeded` — the count
            requested.
        maxAllowed:
          type: integer
          description: Present on `bulk_limit_exceeded` — the plan's per-request cap.
        details:
          description: >-
            Present on some `invalid_argument` (400) responses — a Zod
            `.flatten()` validation breakdown.
          type: object
          properties:
            formErrors:
              type: array
              items:
                type: string
            fieldErrors:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
      required:
        - type
        - title
        - status
        - code
        - error
        - message
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sea_xxxxx
      description: >-
        A Searchable API key, created under **Settings → Workspace →
        Integrations**. Send as `Authorization: Bearer sea_xxxxx`. Every key
        carries one or more scopes (`read`, `write`, `admin`) and may optionally
        be bound to a single project — see [Getting
        started](/api-reference/introduction#authentication).

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.