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

# Patch Credential

> Edit a team's stored integration credential post-onboarding.

Required for credential rotation (token expired), repo URL drift
(the 2026-06-05 ``bleshinsky → AITaskerCo`` GitHub org transfer
left 5 Keystatic credentials pointing at the old URL — the
operator workaround was 5 raw SQL UPDATEs), and
``content_paths`` updates without re-creating the row.

On success, writes a ``TeamAuditLog`` row with
``action_type='integration_credential_updated'`` and a sanitized
diff (field NAMES only — token VALUES are never logged). No-op
patches (every key equals stored) skip the audit write.



## OpenAPI

````yaml /openapi.json patch /api/v1/teams/{team_id}/integrations/{credential_id}
openapi: 3.1.0
info:
  title: AITasker
  version: 0.1.0
servers: []
security: []
paths:
  /api/v1/teams/{team_id}/integrations/{credential_id}:
    patch:
      tags:
        - Team Integrations
      summary: Patch Credential
      description: |-
        Edit a team's stored integration credential post-onboarding.

        Required for credential rotation (token expired), repo URL drift
        (the 2026-06-05 ``bleshinsky → AITaskerCo`` GitHub org transfer
        left 5 Keystatic credentials pointing at the old URL — the
        operator workaround was 5 raw SQL UPDATEs), and
        ``content_paths`` updates without re-creating the row.

        On success, writes a ``TeamAuditLog`` row with
        ``action_type='integration_credential_updated'`` and a sanitized
        diff (field NAMES only — token VALUES are never logged). No-op
        patches (every key equals stored) skip the audit write.
      operationId: >-
        patch_credential_api_v1_teams__team_id__integrations__credential_id__patch
      parameters:
        - name: team_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
            title: Team Id
        - name: credential_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
            title: Credential Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IntegrationCredentialPatchRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CallbackResponse'
        '404':
          description: >-
            Integration credential not found for this team — unknown
            credential_id, or it belongs to another team (404 not 403 to prevent
            cross-team id enumeration).
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
        - HTTPBearer: []
components:
  schemas:
    IntegrationCredentialPatchRequest:
      properties:
        extra_data:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Extra Data
        status:
          anyOf:
            - type: string
              enum:
                - pending
                - credentials_saved
                - connected
                - needs_page_selection
                - expired
                - revoked
                - error
            - type: 'null'
          title: Status
      type: object
      title: IntegrationCredentialPatchRequest
      description: |-
        Partial update to a team's stored ``IntegrationCredential``.

        Wire format
        -----------
        All fields are optional. Omitted fields keep their stored value.
        The shape mirrors RFC 7396 (JSON Merge Patch) at the
        ``extra_data`` level with two AITasker-specific tweaks:

        - **Sensitive fields preserved on blank.** If a sensitive field
          (``github_token`` for Keystatic, ``app_password`` /
          ``application_password`` for WordPress) is sent as an empty
          string or ``null``, the stored ENCRYPTED value is kept (post-
          migration ``e8b3encsec01`` the secret lives in
          ``access_token_encrypted``, not ``extra_data``). This pairs
          with the masked-placeholder UX on the edit form: stored
          secrets render as ``placeholder="••••"`` and only submit when
          the user retypes them. Non-blank sensitive values are
          AES-256-GCM encrypted and written to the encrypted column;
          they NEVER land in ``extra_data``.
        - **Top-level shallow merge.** Nested structures (e.g.
          Keystatic's ``content_paths`` dict) are REPLACED wholesale
          when the patch contains them. Edit-form convention: post the
          full nested dict for any structured field touched.

        See ``team_integration_service.merge_credential_extra_data`` for
        the merge rules + encryption side-channel and unit tests.

        Auth
        ----
        PATCH requires team ownership — same gate as POST ``/config``.
        A ``credential_id`` that doesn't belong to the caller's team
        returns 404, not 403, to prevent enumeration of other teams'
        credential ids.
    CallbackResponse:
      properties:
        provider:
          type: string
          title: Provider
        status:
          type: string
          title: Status
        account_name:
          type: string
          title: Account Name
      type: object
      required:
        - provider
        - status
        - account_name
      title: CallbackResponse
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````