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

# Change the calendar article defaults

> Saves the calendar article defaults with the rules of the Sorank calendar
settings for the same user, through the same command as
`update_calendar_settings` (which keeps `calendar:write`): `timezone`,
`local_time` (`HH:MM`), `publish_as_draft`, `default_instructions` and
`expert_settings`. `expert_settings` is the complete set of calendar-wide
defaults, as `get_settings` returns it: it replaces the current one and an
empty object clears it. `tone`, `image_tone`, `image_style` and
`formality` are validated against their enums (the Sorank settings pages
use the same server validation); `ethnicity` is free text. The defaults
are stored on the calendar: without one the request is refused with
`calendar_required`. A timezone or time change moves the scheduled slots
to keep their local time, declared as `calendar_slots_shifted` in
`effects`; a change that would leave slots outside the scheduling horizon
is refused with `calendar_horizon_exceeded`.




## OpenAPI

````yaml /api-reference/openapi.yaml patch /profiles/{profile_id}/settings/articles
openapi: 3.1.0
info:
  title: SORANK Public API
  version: 1.1.0
  description: >
    Versioned REST contract for user-delegated SORANK access, served under

    `/api/public/v1`. Contract 1.1 adds products, Search Console and AI traffic

    analytics, extended GEO reads and site settings to the same version path.

    Availability of each operation depends on the deployed backend version and

    the public API feature flag.

    OAuth tokens are resource-bound to the REST API. Personal API keys may use

    REST only when their transport grant permits it. Browser session cookies and

    Supabase tokens are not credentials for this surface.


    Every request is checked against the credential's exact scopes, explicit

    profile allowlist and the actor's current profile access. The resource owner

    and billable account are resolved server-side. A revoked or expired
    credential

    is rejected even on idempotent replay and operation polling.


    Eligibility: the user must have completed Sorank onboarding and the accessed

    site must have a valid subscription (the owner's subscription for an agency

    collaborator; a site without a subscription is not eligible). Eligibility is

    checked at OAuth consent, at personal key creation and replacement, and
    again

    on every REST request and MCP tool call, including idempotent replays and

    operation polling. An incomplete onboarding returns 403

    `onboarding_required`; an ineligible site returns 402
    `subscription_required`

    with `details.reason`. `listProfiles` returns only the eligible sites of the

    grant; operations without a profile require at least one eligible site.


    All mutations require `Idempotency-Key`. Repeating an identical command with

    the same key returns its original receipt after fresh authorization; a
    changed

    command with the same key returns 409. An accepted asynchronous command may

    still fail later. Do not treat an accepted batch as wholly complete: an

    article generation batch returns HTTP 202 with a receipt already `succeeded`

    (the acceptance is the command's outcome), and each generated article is
    then

    followed through `getArticle` or `listArticles` from the receipt's accepted
    IDs.

    Settings commands return the resulting settings and the asynchronous

    `effects` they queued.


    Errors use one envelope everywhere, including authentication and rate-limit

    middlewares: `{"error": {"code", "message", "request_id", "retryable",

    "details"?}}`. MCP tool errors carry the same `code`, `message`,

    `request_id`, `retryable` and `details` in the tool error result. Codes are

    stable snake_case identifiers; each operation lists the codes it can return

    in `x-error-codes`, and REST and MCP return the same code for the same
    cause.


    Error statuses: 400 invalid request (`invalid_request`, `invalid_id`,

    `invalid_idempotency_key` and field-specific validation codes such as

    `invalid_limit`, `invalid_range`, `field_not_writable` for a read-only field

    like `website_url`, `invalid_market`, `invalid_competitor_selection`,

    `too_many_competitors` or `invalid_url`); 401

    missing/invalid/expired/revoked credential (`invalid_credential`, with a

    `WWW-Authenticate` challenge carrying `resource_metadata`); 402

    `subscription_required` (with `details.reason` among `paused`, `past_due`,

    `incomplete`, `inactive` when known) or `insufficient_quota` for business

    quotas; 403 `missing_scope` or `onboarding_required`; 404 `not_found` for an

    inaccessible or unknown resource; 409 conflicts: `idempotency_conflict` (the

    key belongs to a different command), `calendar_busy` (retryable),

    `calendar_changed` (read the current calendar before retrying),

    `publication_busy` (retryable), `resource_busy` (retryable),

    `keyword_already_published`, `article_identity_conflict`,

    `slug_already_exists`, `product_catalog_cursor_stale` (restart the

    product list from the first page), `business_brief_revision_conflict`

    (read the settings again), `calendar_required`,

    `youtube_source_replacement_required` (send `replace: true`),

    `youtube_source_purge_in_progress` (retryable, with `Retry-After`);

    `gsc_not_connected`, `ga_not_connected` (connect Google in Sorank); 422
    unsupported provider capability or state

    (`unsupported_capability`, `unsupported_category`, `cms_not_connected`,

    `publication_state_invalid`, `youtube_channel_unsupported`,

    `youtube_channel_not_found`, ...); 403 `google_access_lost` (with

    `details.reason` among `scope_missing`, `property_access_denied`,

    `token_expired`, `ga_permissions_lost`, `ga_property_unavailable`;

    reconnect Google in Sorank); 429 `rate_limited` or `youtube_rate_limited`

    (retryable, with `Retry-After` and `details.retry_after_seconds` when a

    delay is known); 500 `internal_error` for unexpected failures only; 503

    `auth_state_unavailable` or `rate_limit_unavailable` (retryable), with

    access denied until the state recovers, `google_unavailable` or

    `youtube_unavailable` (retryable), or `youtube_feature_disabled` (not

    retryable: the integration is disabled on this deployment); 504 `ga_timeout`
    (retryable with a shorter range).


    Each operation has an MCP tool named by `x-mcp-tool`. Tool arguments are

    flat: `profile_id` and `id` for the path parameters, `idempotency_key` for

    the header, plus the query or body fields of the operation; list and batch

    bounds follow the deployment configuration. A tool result carries exactly

    the REST response body (`data` and `meta`).
servers:
  - url: /api/public/v1
security:
  - OAuth2: []
  - PersonalAPIKey: []
tags:
  - name: Profiles
    description: Accessible SEO profiles, capabilities and business quotas.
  - name: Keywords and GEO
    description: >-
      Existing keyword suggestions, GEO analysis results and GEO aggregates as
      shown by the Sorank dashboard.
  - name: Products
    description: Product catalog discovered on the profile's site.
  - name: Analytics
    description: |
      Live Google Search Console and Google Analytics 4 reads, as shown by the
      Sorank dashboard. Each call queries Google; it may refresh and store the
      site's Google access token, never forces a cache refresh, and is charged
      to a dedicated analytics rate budget lower than other reads. Google
      Analytics exposure is limited to traffic coming from AI assistants.
  - name: Settings
    description: |
      Site settings as the Sorank settings pages show them to the same user,
      published as an allowlist: no owner identifier, homepage screenshot,
      connection field, storage path, signed URL or technical error.
  - name: Articles
    description: Local article reading, editing and generation.
  - name: Calendar
    description: Automatic editorial calendar and slot management.
  - name: Publication
    description: CMS publication, updates and scheduling.
  - name: Operations
    description: Durable command receipts and outcome polling.
paths:
  /profiles/{profile_id}/settings/articles:
    patch:
      tags:
        - Settings
      summary: Change the calendar article defaults
      description: >
        Saves the calendar article defaults with the rules of the Sorank
        calendar

        settings for the same user, through the same command as

        `update_calendar_settings` (which keeps `calendar:write`): `timezone`,

        `local_time` (`HH:MM`), `publish_as_draft`, `default_instructions` and

        `expert_settings`. `expert_settings` is the complete set of
        calendar-wide

        defaults, as `get_settings` returns it: it replaces the current one and
        an

        empty object clears it. `tone`, `image_tone`, `image_style` and

        `formality` are validated against their enums (the Sorank settings pages

        use the same server validation); `ethnicity` is free text. The defaults

        are stored on the calendar: without one the request is refused with

        `calendar_required`. A timezone or time change moves the scheduled slots

        to keep their local time, declared as `calendar_slots_shifted` in

        `effects`; a change that would leave slots outside the scheduling
        horizon

        is refused with `calendar_horizon_exceeded`.
      operationId: updateArticleSettings
      parameters:
        - $ref: '#/components/parameters/ProfileID'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateArticleSettingsRequest'
      responses:
        '200':
          $ref: '#/components/responses/SettingsMutation'
        '401':
          $ref: '#/components/responses/Unauthorized'
        default:
          $ref: '#/components/responses/Error'
      security:
        - OAuth2:
            - settings:write
        - PersonalAPIKey: []
components:
  parameters:
    ProfileID:
      name: profile_id
      in: path
      required: true
      description: >-
        Profile UUID; must be in the grant allowlist and currently accessible to
        the actor.
      schema:
        $ref: '#/components/schemas/UUID'
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        Opaque client command key of 1 to 128 visible ASCII characters. UUID
        recommended; reuse only for the same command.
      schema:
        type: string
        minLength: 1
        maxLength: 128
        pattern: ^[!-~]+$
  schemas:
    UpdateArticleSettingsRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      description: At least one field.
      properties:
        timezone:
          type: string
          minLength: 1
          description: IANA timezone such as Europe/Paris.
        local_time:
          type: string
          pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$
          description: Local wall-clock publication time HH:MM.
        publish_as_draft:
          type: boolean
          description: true sends generated articles to the CMS as drafts.
        default_instructions:
          type: string
          description: Default editorial instructions; an empty string clears them.
        expert_settings:
          $ref: '#/components/schemas/ArticleExpertSettingsInput'
    UUID:
      type: string
      format: uuid
    ArticleExpertSettingsInput:
      type: object
      additionalProperties: false
      description: >-
        Complete calendar-wide article defaults; they replace the current ones
        and an empty object clears them. An absent field inherits the system
        default.
      properties:
        tone:
          type: string
          enum:
            - professional
            - casual
            - friendly
            - authoritative
            - educational
            - engaging
            - informative
            - conversational
            - persuasive
            - humorous
        formality:
          type: string
          enum:
            - formal
            - informal
        generate_images:
          type: boolean
        image_source_mode:
          type: string
          enum:
            - ai_only
            - gallery_then_ai
            - products_gallery_then_ai
            - gallery_only
            - none
        image_tone:
          type: string
          enum:
            - auto
            - professional
            - casual
            - friendly
            - authoritative
            - humorous
        image_style:
          type: string
          enum:
            - auto
            - product_photography
            - lifestyle
            - editorial
            - flat_lay
            - stock_photo
            - illustration
        ethnicity:
          type: string
          maxLength: 50
          description: Appearance of people in generated images
          in free text.: null
        image_instructions:
          type: string
          maxLength: 2000
        conclusion_enabled:
          type: boolean
        cta_enabled:
          type: boolean
        cta_title:
          type: string
          description: An empty title lets the writer choose.
        include_homepage_screenshot:
          type: boolean
        include_youtube_videos:
          type: boolean
    SettingsMutationEnvelope:
      type: object
      additionalProperties: false
      required:
        - data
        - meta
      properties:
        data:
          $ref: '#/components/schemas/SettingsMutationData'
        meta:
          $ref: '#/components/schemas/MutationMeta'
    ErrorEnvelope:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
      examples:
        - error:
            code: insufficient_quota
            message: The current article generation quota is exhausted.
            request_id: 33333333-3333-4333-8333-333333333333
            retryable: false
    SettingsMutationData:
      type: object
      additionalProperties: false
      required:
        - settings
        - effects
        - receipt
      properties:
        settings:
          $ref: '#/components/schemas/SettingsData'
        effects:
          type: array
          description: >-
            Asynchronous effects the command queued, as the Sorank settings
            pages do. A replay returns the effects of the first execution.
          items:
            type: string
            enum:
              - geo_questions_regeneration_queued
              - backlink_refresh_queued
              - image_gallery_analysis_queued
              - youtube_sync_queued
              - youtube_purge_queued
              - calendar_slots_shifted
        receipt:
          $ref: '#/components/schemas/CommandReceipt'
    MutationMeta:
      type: object
      additionalProperties: false
      required:
        - request_id
        - operation_id
      properties:
        request_id:
          type: string
          minLength: 1
        operation_id:
          $ref: '#/components/schemas/UUID'
    ErrorBody:
      type: object
      additionalProperties: false
      required:
        - code
        - message
        - request_id
        - retryable
      properties:
        code:
          type: string
          description: Stable public error code; never a raw SQL, CMS or provider error.
          examples:
            - insufficient_quota
            - unsupported_capability
        message:
          type: string
        request_id:
          $ref: '#/components/schemas/UUID'
        retryable:
          type: boolean
          description: |
            True when repeating the same request later, unchanged and with the
            same Idempotency-Key, may succeed (transient busy state, rate limit,
            unavailable authorization or rate-limit state). False when the
            request or the resource state must change first.
        details:
          type: object
          additionalProperties: true
          description: >-
            Safe structured details only (for example `reason` for
            `subscription_required`); no tokens, prompts or internal commands.
    SettingsData:
      type: object
      additionalProperties: false
      required:
        - site
        - business_brief
        - competitors
        - articles
        - visuals
        - youtube
      properties:
        site:
          $ref: '#/components/schemas/SettingsSiteData'
        business_brief:
          $ref: '#/components/schemas/SettingsBusinessBriefData'
        competitors:
          $ref: '#/components/schemas/SettingsCompetitorsData'
        articles:
          $ref: '#/components/schemas/SettingsArticlesData'
        visuals:
          $ref: '#/components/schemas/SettingsVisualsData'
        youtube:
          $ref: '#/components/schemas/SettingsYouTubeData'
    CommandReceipt:
      type: object
      additionalProperties: false
      description: |
        Durable receipt of a command; poll getOperation with operation_id until
        the status is terminal (`succeeded` or `failed`; `verification_pending`
        means the CMS outcome is still being verified). An article generation
        receipt is `succeeded` as soon as the batch is accepted.
      required:
        - operation_id
        - status
        - resource_id
      properties:
        operation_id:
          $ref: '#/components/schemas/UUID'
        status:
          type: string
          enum:
            - accepted
            - running
            - succeeded
            - failed
            - verification_pending
        resource_id:
          $ref: '#/components/schemas/UUID'
    SettingsSiteData:
      type: object
      additionalProperties: false
      required:
        - website_url
        - business_name
        - brand_color
        - market
      properties:
        website_url:
          type:
            - string
            - 'null'
          format: uri
          description: >-
            Read-only site URL; null when the stored URL cannot be exposed
            safely. It cannot be changed through the API.
        business_name:
          type: string
        brand_color:
          type:
            - string
            - 'null'
          description: Profile brand color
          null when unset.: null
        market:
          $ref: '#/components/schemas/SettingsMarketData'
    SettingsBusinessBriefData:
      type: object
      additionalProperties: false
      required:
        - status
        - revision
        - document
      properties:
        status:
          type: string
          enum:
            - pending
            - ready
            - failed
          description: Generation status of the business brief.
        revision:
          type: integer
          minimum: 0
          description: Current revision; the `expected_revision` of a document replacement.
        document:
          description: Editable business document, null until one exists.
          oneOf:
            - $ref: '#/components/schemas/BusinessDocumentData'
            - type: 'null'
    SettingsCompetitorsData:
      type: object
      additionalProperties: false
      required:
        - domains
        - max_selections
      properties:
        domains:
          type: array
          items:
            type: string
        max_selections:
          type: integer
          minimum: 0
    SettingsArticlesData:
      type: object
      additionalProperties: false
      description: >-
        Calendar article defaults. Without a calendar only calendar_exists is
        present.
      required:
        - calendar_exists
      properties:
        calendar_exists:
          type: boolean
        timezone:
          type: string
          description: IANA timezone of the calendar.
        local_time:
          type: string
          pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$
          description: Local wall-clock publication time HH:MM.
        publish_as_draft:
          type: boolean
        default_instructions:
          type: string
        expert_settings:
          $ref: '#/components/schemas/ArticleExpertSettingsData'
    SettingsVisualsData:
      type: object
      additionalProperties: false
      required:
        - image_mode
        - covers
      properties:
        image_mode:
          type:
            - string
            - 'null'
          enum:
            - ai_only
            - gallery_then_ai
            - products_gallery_then_ai
            - gallery_only
            - none
            - null
          description: Image source mode, null until one is configured.
        covers:
          $ref: '#/components/schemas/SettingsCoversData'
    SettingsYouTubeData:
      type: object
      additionalProperties: false
      description: Connected YouTube channel. Without a channel only connected is present.
      required:
        - connected
      properties:
        connected:
          type: boolean
        channel_title:
          type: string
        canonical_url:
          type: string
          format: uri
        enabled:
          type: boolean
        sync_status:
          type: string
          enum:
            - syncing
            - synced
            - error
            - disabled
            - disconnecting
        last_synced_at:
          $ref: '#/components/schemas/Timestamp'
        video_count:
          type: integer
          minimum: 0
    SettingsMarketData:
      type: object
      additionalProperties: false
      required:
        - language
        - country
        - scope
        - city
      properties:
        language:
          type: string
        country:
          type: string
        scope:
          type:
            - string
            - 'null'
          enum:
            - city
            - national
            - international
            - null
        city:
          type:
            - string
            - 'null'
    BusinessDocumentData:
      type: object
      additionalProperties: false
      required:
        - schema_version
        - sections
        - offers
      properties:
        schema_version:
          type: integer
        sections:
          type: array
          items:
            $ref: '#/components/schemas/BusinessDocumentSectionData'
        offers:
          type: array
          items:
            $ref: '#/components/schemas/BusinessDocumentOfferData'
    ArticleExpertSettingsData:
      type: object
      additionalProperties: false
      description: >-
        Calendar-wide article defaults; an absent field inherits the system
        default.
      properties:
        tone:
          type: string
        formality:
          type: string
        generate_images:
          type: boolean
        image_source_mode:
          type: string
          enum:
            - ai_only
            - gallery_then_ai
            - products_gallery_then_ai
            - gallery_only
            - none
        image_tone:
          type: string
        image_style:
          type: string
        ethnicity:
          type: string
        image_instructions:
          type: string
        conclusion_enabled:
          type: boolean
        cta_enabled:
          type: boolean
        cta_title:
          type: string
          description: An empty title lets the writer choose.
        include_homepage_screenshot:
          type: boolean
        include_youtube_videos:
          type: boolean
    SettingsCoversData:
      type: object
      additionalProperties: false
      required:
        - enabled
        - default_preset
      properties:
        enabled:
          type: boolean
        default_preset:
          $ref: '#/components/schemas/CoverPresetData'
    Timestamp:
      type: string
      format: date-time
      description: RFC3339 timestamp with an explicit UTC offset.
    BusinessDocumentSectionData:
      type: object
      additionalProperties: false
      required:
        - id
        - text
      properties:
        id:
          type: string
          enum:
            - identity
            - offerings
            - audiences
            - needs_use_cases
            - positioning_markets
            - expertise
            - sales_journey
            - operations_delivery
            - evidence_engagement
            - editorial_constraints
        text:
          type: string
    BusinessDocumentOfferData:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - description
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
    CoverPresetData:
      type: object
      additionalProperties: false
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - builtin
            - custom
        key:
          type: string
          enum:
            - sketch
            - watercolor
            - illustration
          description: Built-in preset.
        id:
          $ref: '#/components/schemas/UUID'
  responses:
    SettingsMutation:
      description: >-
        Settings read after the command, queued effects and durable receipt
        about the profile.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SettingsMutationEnvelope'
    Unauthorized:
      description: Credential missing, invalid, expired or revoked.
      headers:
        WWW-Authenticate:
          description: Bearer challenge with configured resource metadata where applicable.
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Error:
      description: Error response; see status-to-code mapping in API description.
      headers:
        Retry-After:
          description: >-
            Seconds until retry when HTTP 429 is returned, or with 409
            youtube_source_purge_in_progress.
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    OAuth2:
      type: oauth2
      description: |
        Authorization-code grant with PKCE S256, exact scopes, explicit resource
        audience and a persisted consent grant. Obtain endpoint URLs from server
        discovery in the target environment. Refresh tokens remain client-bound.
      flows:
        authorizationCode:
          authorizationUrl: /oauth/authorize
          tokenUrl: /oauth/token
          refreshUrl: /oauth/token
          scopes:
            profiles:read: Read authorized profiles and CMS capabilities.
            quotas:read: Read business quotas.
            keywords:read: Read existing keyword suggestions.
            geo:read: Read existing GEO analyses.
            articles:read: Read articles and generation results.
            articles:write: Edit allowed local article fields.
            articles:generate: Generate articles.
            articles:delete: Delete eligible local articles.
            calendar:read: Read calendar and slots.
            calendar:write: Manage calendar and slots.
            articles:publish: Schedule and manage CMS publication.
            products:read: Read the product catalog.
            analytics:read: Read Search Console and AI traffic analytics.
            settings:read: Read site, article, visual and YouTube settings.
            settings:write: >-
              Change site, business, competitor, article, visual and YouTube
              settings. Some changes queue asynchronous regeneration or
              synchronization.
    PersonalAPIKey:
      type: http
      scheme: bearer
      bearerFormat: Personal API key
      description: >
        Send the one-time personal API key in Authorization: Bearer. Exact
        scopes,

        explicit profiles, expiry, revocation and transport are verified
        server-side.

        This is not an OAuth access token or a browser session.

````

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