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

# Dry-run an archive against every import screen

> Runs the SAME screens `ingestPageArchive` runs — the manifest schema, the archive and path guards, the dependency allowlist, recipe resolution, the conformance gate and every declared ceiling — over the archive you send, and answers the FULL list of refusals at once. It spends no build, mints no revision and writes nothing: no site is created or modified, and sending the same archive to the import afterwards is what actually imports it. Read `screensRun` before treating an empty `refusals` as a guarantee: anything we cannot decide without compiling your project stays a build result. Authenticated exactly like the import, and metered as a read.



## OpenAPI

````yaml /openapi.json post /api/pages/ingest/validate
openapi: 3.1.0
info:
  title: Goosy Bear API
  version: 1.0.0-beta
  summary: Manage workspace content, projects, Data tables, Pages, library and credits.
  description: >-
    BETA. This API is in beta: paths, request shapes and response envelopes may
    change, and every response carries an `x-goosy-api: beta` header for as long
    as that is true. Pin the OpenAPI document you generated against and
    re-generate when it changes.
servers:
  - url: https://app.goosybear.ai
    description: Production
security:
  - tenantApiKey: []
paths:
  /api/pages/ingest/validate:
    post:
      tags:
        - pages
      summary: Dry-run an archive against every import screen
      description: >-
        Runs the SAME screens `ingestPageArchive` runs — the manifest schema,
        the archive and path guards, the dependency allowlist, recipe
        resolution, the conformance gate and every declared ceiling — over the
        archive you send, and answers the FULL list of refusals at once. It
        spends no build, mints no revision and writes nothing: no site is
        created or modified, and sending the same archive to the import
        afterwards is what actually imports it. Read `screensRun` before
        treating an empty `refusals` as a guarantee: anything we cannot decide
        without compiling your project stays a build result. Authenticated
        exactly like the import, and metered as a read.
      operationId: validatePageArchive
      parameters:
        - name: workspace
          in: query
          required: false
          description: >-
            Which workspace to act in — its slug or id. The SAME argument every
            workspace-scoped tool takes, resolved the same way: an account-wide
            key may select any workspace its holder currently reaches, a
            workspace-pinned key may only name its own, and a call that names
            nothing falls through to your default. It is a QUERY parameter
            rather than a body field on purpose: this endpoint's body is a
            multipart upload, and reading a field out of it would decode that
            body before the bearer and the rate limiter had run. Ignored for a
            signed-in session, whose workspace comes from its own claims.
          schema:
            type: string
            minLength: 1
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              additionalProperties: false
              properties:
                file:
                  type: string
                  format: binary
                  description: The single ZIP archive to screen.
                siteId:
                  type: string
                  format: uuid
                  description: >-
                    The site you intend to import onto. OPTIONAL, and it is NOT
                    looked up — only its shape is checked. It decides one thing:
                    whether the managed-adoption screen applies, which it does
                    only when you are replacing the source of an existing site.
              required:
                - file
      responses:
        '200':
          description: >-
            The archive was screened. `data.ok` is the verdict; a `200` with
            `data.ok: false` is the ordinary answer for an archive that would be
            refused.
          headers:
            x-goosy-api:
              description: Present while this API is in beta; the value is `beta`.
              schema:
                type: string
                const: beta
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    const: true
                  data:
                    type: object
                    properties:
                      ok:
                        type: boolean
                        description: >-
                          `true` when nothing refused — importing these exact
                          bytes would be accepted. `false` means `refusals` is
                          non-empty.
                      kind:
                        type: string
                        enum:
                          - bundle
                          - prebuilt
                          - source:astro
                          - source:next
                          - source:vite
                          - source:static-generator
                          - not_a_site
                        description: What the archive was classified as, from its CONTENTS.
                      markers:
                        type: array
                        items:
                          type: string
                        description: The classifier's evidence, strongest first.
                      recipe:
                        type:
                          - string
                          - 'null'
                        description: >-
                          The build recipe your project would be conformed onto,
                          when one resolved.
                      deploySource:
                        anyOf:
                          - type: string
                            enum:
                              - declared
                              - legacy
                              - synthesized
                          - type: 'null'
                        description: >-
                          Where the deploy manifest came from — your
                          `pages.deploy.json`, a translated legacy
                          `pages.functions.json`, or one we synthesized.
                      siteImportEnabled:
                        type: boolean
                        description: >-
                          Whether source-project importing is switched on for
                          your account. It CHANGES which screens apply, so two
                          answers for the same bytes are only comparable when
                          this matches.
                      uploadBytes:
                        type: integer
                        description: The compressed bytes you sent.
                      screensRun:
                        type: array
                        items:
                          type: string
                        description: >-
                          Which screens actually ran. A screen that did not
                          apply to this archive is ABSENT — so an empty
                          `refusals` is 'these screens passed', never
                          'everything passed'. Anything we cannot decide without
                          compiling your project (a type error, a failing
                          integration) is not here and stays a build result.
                      refusals:
                        type: array
                        items:
                          type: object
                          properties:
                            screen:
                              type: string
                              description: Which screen said no — a member of `screensRun`.
                            where:
                              type: string
                              enum:
                                - upload
                                - read
                                - build
                                - publish
                              description: >-
                                The stage a real import would have discovered it
                                at.
                            code:
                              type: string
                              description: >-
                                The same error code the import itself answers
                                with.
                            reason:
                              type:
                                - string
                                - 'null'
                              description: The specific guard, when the screen named one.
                            message:
                              type: string
                              description: >-
                                The refusal sentence, exactly as an import
                                states it.
                            path:
                              type:
                                - string
                                - 'null'
                              description: The offending file, when the screen named one.
                          required:
                            - screen
                            - where
                            - code
                            - reason
                            - message
                            - path
                          additionalProperties: false
                        description: >-
                          EVERY refusal, not the first one. The list is in the
                          order the rail screens run, so fixing them top-down is
                          fixing them in the order they would otherwise have
                          cost you builds.
                      measurements:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              description: >-
                                Stable dotted id — `functions.modules`,
                                `archive.entries`.
                            label:
                              type: string
                              description: What it counts, in one phrase.
                            measured:
                              type: number
                              description: What your archive measured.
                            limit:
                              type: number
                              description: The ceiling it is measured against.
                            unit:
                              type: string
                              enum:
                                - bytes
                                - files
                                - characters
                                - milliseconds
                                - requests
                            over:
                              type: boolean
                              description: >-
                                `true` when an import would refuse on this
                                number.
                          required:
                            - id
                            - label
                            - measured
                            - limit
                            - unit
                            - over
                          additionalProperties: false
                        description: >-
                          Every ceiling this archive could be measured against,
                          each beside the number you sent — so a project at 56
                          of 512 modules can see its headroom, not just the
                          absence of a refusal.
                    required:
                      - ok
                      - kind
                      - markers
                      - recipe
                      - deploySource
                      - siteImportEnabled
                      - uploadBytes
                      - screensRun
                      - refusals
                      - measurements
                    additionalProperties: false
                required:
                  - ok
                  - data
                additionalProperties: false
        '400':
          description: >-
            The body was not multipart/form-data with a `file` field, or
            `siteId` was not the shape of a site id. A refusal the SCREENS made
            is a `200` with `data.ok: false`, not a `400`.
          headers:
            x-goosy-api:
              description: Present while this API is in beta; the value is `beta`.
              schema:
                type: string
                const: beta
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageIngestError'
        '401':
          description: No bearer credential, or one that did not resolve.
          headers:
            x-goosy-api:
              description: Present while this API is in beta; the value is `beta`.
              schema:
                type: string
                const: beta
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageIngestError'
        '403':
          description: >-
            Programmatic access is unavailable to this holder, the named
            workspace is outside the key's scope, or importing is switched off
            for this account. `workspace_ambiguous` and `workspace_unreachable`
            also answer here, with the slugs appended to `error.message`.
          headers:
            x-goosy-api:
              description: Present while this API is in beta; the value is `beta`.
              schema:
                type: string
                const: beta
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageIngestError'
        '429':
          description: >-
            The API key is over its rate limit. Wait for `Retry-After` before
            retrying.
          headers:
            x-goosy-api:
              description: Present while this API is in beta; the value is `beta`.
              schema:
                type: string
                const: beta
            Retry-After:
              description: Whole seconds to wait before retrying.
              schema:
                type: integer
                minimum: 1
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageIngestError'
        '500':
          description: An unexpected server error occurred while screening the archive.
          headers:
            x-goosy-api:
              description: Present while this API is in beta; the value is `beta`.
              schema:
                type: string
                const: beta
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageIngestError'
components:
  schemas:
    PageIngestError:
      type: object
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
          required:
            - code
            - message
          additionalProperties: false
      required:
        - ok
        - error
      additionalProperties: false
      description: >-
        A Pages import that did not succeed. ⚠️ Unlike a tool refusal, this
        envelope has NO `workspaces` array — on `workspace_ambiguous` and
        `workspace_unreachable` the slugs you may pass are appended to
        `error.message` instead.
  securitySchemes:
    tenantApiKey:
      type: http
      scheme: bearer
      description: >-
        An API key minted at Settings › API & MCP. Send it as `Authorization:
        Bearer <key>`. A key carries its holder's own permissions, resolved on
        every call — revoking a membership closes the key's reach immediately.
        Keep it in an environment variable (`GOOSY_API_KEY`), never in a
        committed file.

````