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

# start_skin_scan

> Start a scan from an inline photo or an upload link.

Creates a scan and returns immediately with `run_id` and `access_token`. Analysis runs in the background; poll it with [`get_skin_scan`](/tools/get-skin-scan). Choose between the [two photo paths](/scan-lifecycle#photo-intake):

* **With `photo` and `consent`:** the scan is queued at once.
* **Without `photo`:** returns an `upload_url` for the person to open.

**Common errors:** `admissions_paused`, `consent_page_required`, `invalid_image`, `invalid_image_dimensions`, `payload_too_large`, `rate_limited`, `scan_limit_reached`. See [errors](/errors).

|             |                                                                      |
| ----------- | -------------------------------------------------------------------- |
| Tool        | `start_skin_scan`                                                    |
| Listed on   | `/mcp` (guest) and `/mcp/account` (OAuth)                            |
| Annotations | `readOnlyHint: false` `destructiveHint: false` `openWorldHint: true` |

## Input

Sent as `params.arguments` of `tools/call`. Unknown keys are rejected with `invalid_input`.

<ParamField body="source" type="enum<string>">
  One of `muse`, `mcp`, `web`, `other`. Default `"other"`.
</ParamField>

<ParamField body="campaign" type="string">
  Pattern `^[a-zA-Z0-9_-]{1,64}$`.
</ParamField>

<ParamField body="photo" type="object">
  The person's photo, sent directly. Requires consent. Omit it to get a link the person opens themselves instead.

  <Expandable title="properties">
    <ParamField body="data" type="string" required>
      The person's photo, base64 encoded. At most 5 MB decoded and 8 megapixels. A full-resolution photo from a modern camera roll is usually over the pixel limit — if it is refused, call again without `photo` and hand the person the upload\_url, where their browser resizes it.
    </ParamField>

    <ParamField body="media_type" type="enum<string>" required>
      Must match the actual bytes.

      One of `image/jpeg`, `image/png`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="consent" type="object">
  Required with photo, and only meaningful with it.

  <Expandable title="properties">
    <ParamField body="adult" type="true" required>
      The person confirmed to you that they are 18 or older.
    </ParamField>

    <ParamField body="own_photo" type="true" required>
      The person confirmed this is a photo of their own face.
    </ParamField>

    <ParamField body="agreed_to_processing" type="true" required>
      You told the person the photo goes to Skan and Haut.AI for cosmetic analysis and is deleted within an hour, and they agreed.
    </ParamField>
  </Expandable>
</ParamField>

## Output

Returned as `structuredContent`, and as the same object serialised in `content[0].text`. On failure the result has `isError: true` and an [error body](/errors) instead.

<ResponseField name="next_action" type="enum<string>" required>
  One of `upload_photo`, `check_again`, `show_result`, `stop`.
</ResponseField>

<ResponseField name="retry_after_seconds" type="integer" required>
  Range 0–9007199254740991.
</ResponseField>

<ResponseField name="max_auto_checks" type="integer" required>
  Range 0–9007199254740991.
</ResponseField>

<ResponseField name="processing_deadline_at" type="number | null" required />

<ResponseField name="message" type="string" required />

<ResponseField name="instructions" type="string" required />

<ResponseField name="run_id" type="string" required>
  Format `uuid`.
</ResponseField>

<ResponseField name="access_token" type="string" required>
  Pattern `^[a-f0-9]{64}$`.
</ResponseField>

<ResponseField name="upload_url" type="string | null" required>
  Format `uri`.
</ResponseField>

<ResponseField name="expires_at" type="number" required />

<ResponseField name="status" type="enum<string>" required>
  One of `awaiting_upload`, `queued`.
</ResponseField>

<ResponseField name="consent_recorded" type="enum<string> | null" required>
  One of `person_on_skan_page`, `agent_attested`.
</ResponseField>

<AccordionGroup>
  <Accordion title="Description the model receives">
    ```text theme={null}
    Start a free Skan cosmetic skin scan. There are two ways to supply the photo, and you must choose deliberately.

    DIRECT, when your host allows it: send the person's photo with this call in `photo`, together with `consent`. This needs the photo file's actual bytes, which many hosts do not give you: seeing an image is not the same as having its file. Never type, reconstruct or regenerate base64 for an image you can only see; if you do not have the bytes, use the link. Before you send a photo, you must have (1) asked the person for a photo of their own face and received it from them in this conversation, (2) told them the photo is sent to Skan and its analysis provider Haut.AI for cosmetic appearance analysis and is deleted within an hour, and (3) confirmed they are 18 or older. Only then set the three `consent` fields. Never send a photo the person did not give you for this purpose, never send an image of anyone else, never send one you generated or found, and never assert `consent` you did not actually obtain. If you cannot confirm all three things, omit `photo` and use the link instead.

    LINK, which works on every host: omit `photo` and you get an `upload_url`. Give it to the person and they consent and choose a photo on Skan's own page. Use this whenever you do not have the photo file's bytes, whenever you are unsure, whenever the person would rather not send a photo through this conversation, or whenever `consent_page_required` is returned.

    Either way this returns immediately with a session, not analysis results; analysis runs in the background. Follow next_action and instructions. `source` is self-reported attribution, not verified platform identity.
    ```
  </Accordion>

  <Accordion title="Input JSON Schema">
    ```json theme={null}
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "source": {
          "default": "other",
          "type": "string",
          "enum": [
            "muse",
            "mcp",
            "web",
            "other"
          ]
        },
        "campaign": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_-]{1,64}$"
        },
        "photo": {
          "description": "The person's photo, sent directly. Requires consent. Omit it to get a link the person opens themselves instead.",
          "type": "object",
          "properties": {
            "data": {
              "type": "string",
              "description": "The person's photo, base64 encoded. At most 5 MB decoded and 8 megapixels. A full-resolution photo from a modern camera roll is usually over the pixel limit — if it is refused, call again without `photo` and hand the person the upload_url, where their browser resizes it."
            },
            "media_type": {
              "type": "string",
              "enum": [
                "image/jpeg",
                "image/png"
              ],
              "description": "Must match the actual bytes."
            }
          },
          "required": [
            "data",
            "media_type"
          ],
          "additionalProperties": false
        },
        "consent": {
          "description": "Required with photo, and only meaningful with it.",
          "type": "object",
          "properties": {
            "adult": {
              "type": "boolean",
              "const": true,
              "description": "The person confirmed to you that they are 18 or older."
            },
            "own_photo": {
              "type": "boolean",
              "const": true,
              "description": "The person confirmed this is a photo of their own face."
            },
            "agreed_to_processing": {
              "type": "boolean",
              "const": true,
              "description": "You told the person the photo goes to Skan and Haut.AI for cosmetic analysis and is deleted within an hour, and they agreed."
            }
          },
          "required": [
            "adult",
            "own_photo",
            "agreed_to_processing"
          ],
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    }
    ```
  </Accordion>

  <Accordion title="Output JSON Schema">
    ```json theme={null}
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "next_action": {
          "type": "string",
          "enum": [
            "upload_photo",
            "check_again",
            "show_result",
            "stop"
          ]
        },
        "retry_after_seconds": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "max_auto_checks": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "processing_deadline_at": {
          "type": [
            "number",
            "null"
          ]
        },
        "message": {
          "type": "string"
        },
        "instructions": {
          "type": "string"
        },
        "run_id": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
        },
        "access_token": {
          "type": "string",
          "pattern": "^[a-f0-9]{64}$"
        },
        "upload_url": {
          "anyOf": [
            {
              "type": "string",
              "format": "uri"
            },
            {
              "type": "null"
            }
          ]
        },
        "expires_at": {
          "type": "number"
        },
        "status": {
          "type": "string",
          "enum": [
            "awaiting_upload",
            "queued"
          ]
        },
        "consent_recorded": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "person_on_skan_page",
                "agent_attested"
              ]
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "next_action",
        "retry_after_seconds",
        "max_auto_checks",
        "processing_deadline_at",
        "message",
        "instructions",
        "run_id",
        "access_token",
        "upload_url",
        "expires_at",
        "status",
        "consent_recorded"
      ],
      "additionalProperties": false
    }
    ```
  </Accordion>
</AccordionGroup>

<RequestExample>
  ```json Request (upload link) theme={null}
  {
    "jsonrpc": "2.0", "id": 1, "method": "tools/call",
    "params": { "name": "start_skin_scan", "arguments": { "source": "mcp" } }
  }
  ```

  ```json Request (direct photo) theme={null}
  {
    "jsonrpc": "2.0", "id": 1, "method": "tools/call",
    "params": {
      "name": "start_skin_scan",
      "arguments": {
        "source": "mcp",
        "photo": { "data": "/9j/4AAQSkZJRgABAQ…", "media_type": "image/jpeg" },
        "consent": { "adult": true, "own_photo": true, "agreed_to_processing": true }
      }
    }
  }
  ```
</RequestExample>

<ResponseExample>
  ```json Response (direct photo, structuredContent) theme={null}
  {
    "run_id": "db56607f-edeb-47f3-bc5d-812d7af19adb",
    "access_token": "<64 hex characters>",
    "upload_url": null,
    "expires_at": 1790790705460,
    "status": "queued",
    "consent_recorded": "agent_attested",
    "next_action": "check_again",
    "retry_after_seconds": 10,
    "max_auto_checks": 12,
    "processing_deadline_at": 1790186505703,
    "message": "Skan is analyzing your photo.",
    "instructions": "If your host can wait, wait at least retry_after_seconds before calling get_skin_scan …"
  }
  ```
</ResponseExample>
