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

> ## Agent Instructions
> Describe what the running code does. Do not invent hosted pricing, launch dates, or security guarantees. Share and viewer links are a configured base URL plus an artifact path. Show localhost and a placeholder origin such as https://library.example.com.

# Unlock a password-protected share

> No session. JSON `{ password }`. A matching password returns `{ grant: { token, expiresAt } }` valid for 15 minutes. A link with no password returns `{ grant: null }`. A wrong password is `401 PASSWORD_INCORRECT`. An inactive link is `404 ARTIFACT_NOT_FOUND`. A missing or non-JSON body is `400 INVALID_REQUEST` before any password throttle is charged. Client failures allow 10 per 10 minutes. Share failures allow 30 per 10 minutes, charged only after a wrong password. An exhausted share is `429 RATE_LIMITED` with `Retry-After` and no hash verification. A saturated Argon2 gate, or a share whose remaining slots are all in flight, is `503 UNLOCK_UNAVAILABLE` with `Retry-After`. This route ignores a leftover session cookie and does not apply the cookie origin guard.



## OpenAPI

````yaml /api-reference/openapi.json post /api/v1/public/artifacts/{id}/{token}/unlock
openapi: 3.1.0
info:
  title: Factrove HTTP API
  version: 0.1.0
  description: >-
    Generated from `@factrove/contracts` schemas and the HTTP operation catalog.
    Product routes live under `/api/v1`. Auth and API key management live under
    `/api/auth`. Do not treat this file as hand-written. Run `pnpm docs:openapi`
    after contract or route-catalog changes.
servers:
  - url: http://localhost:3001
    description: Local development API origin
  - url: https://api.example.com
    description: Example self-hosted API origin
security: []
tags:
  - name: Health
    description: Process liveness.
  - name: Auth
    description: Sessions and API keys.
  - name: Artifacts
    description: Upload, list, rename, delete, detail, and content.
  - name: Shares
    description: Owner public-link and password controls.
  - name: Public
    description: Anonymous share detail, unlock, and HTML.
paths:
  /api/v1/public/artifacts/{id}/{token}/unlock:
    post:
      tags:
        - Public
      summary: Unlock a password-protected share
      description: >-
        No session. JSON `{ password }`. A matching password returns `{ grant: {
        token, expiresAt } }` valid for 15 minutes. A link with no password
        returns `{ grant: null }`. A wrong password is `401 PASSWORD_INCORRECT`.
        An inactive link is `404 ARTIFACT_NOT_FOUND`. A missing or non-JSON body
        is `400 INVALID_REQUEST` before any password throttle is charged. Client
        failures allow 10 per 10 minutes. Share failures allow 30 per 10
        minutes, charged only after a wrong password. An exhausted share is `429
        RATE_LIMITED` with `Retry-After` and no hash verification. A saturated
        Argon2 gate, or a share whose remaining slots are all in flight, is `503
        UNLOCK_UNAVAILABLE` with `Retry-After`. This route ignores a leftover
        session cookie and does not apply the cookie origin guard.
      operationId: unlockPublicArtifact
      parameters:
        - name: id
          in: path
          required: true
          description: Artifact UUID.
          schema:
            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)$
        - name: token
          in: path
          required: true
          description: >-
            Public share token from the owner share URL. The stored column is a
            hash, not this value.
          schema:
            type: string
            minLength: 1
      requestBody:
        required: true
        description: The link password.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicUnlockRequest'
            example:
              password: example-link-password
      responses:
        '200':
          description: A grant for a protected link, or null when the link has no password.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicUnlockEnvelope'
              example:
                grant:
                  token: g1.example
                  expiresAt: '2026-10-21T12:15:00.000Z'
        '400':
          description: Missing or non-JSON body. Code `INVALID_REQUEST`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                error:
                  code: INVALID_REQUEST
                  message: Send the password as a string.
        '401':
          description: Wrong password. Code `PASSWORD_INCORRECT`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                error:
                  code: PASSWORD_INCORRECT
                  message: That password is not right.
        '404':
          description: The link is not active. Code `ARTIFACT_NOT_FOUND`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                error:
                  code: ARTIFACT_NOT_FOUND
                  message: Artifact was not found.
        '429':
          description: >-
            Too many password attempts for this client or this share. Code
            `RATE_LIMITED`. Every password-throttle 429 sends Retry-After.
            Counters are in memory on each API process.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                error:
                  code: RATE_LIMITED
                  message: Too many password attempts. Try again later.
        '503':
          description: >-
            Password verification is busy. Code `UNLOCK_UNAVAILABLE`. Includes
            Retry-After.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                error:
                  code: UNLOCK_UNAVAILABLE
                  message: Password verification is busy. Try again.
      security: []
components:
  schemas:
    PublicUnlockRequest:
      type: object
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
      required:
        - password
      additionalProperties: false
    PublicUnlockEnvelope:
      type: object
      properties:
        grant:
          anyOf:
            - type: object
              properties:
                token:
                  type: string
                  minLength: 1
                expiresAt:
                  type: string
                  format: date-time
                  pattern: >-
                    ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
              required:
                - token
                - expiresAt
              additionalProperties: false
            - type: 'null'
      required:
        - grant
      additionalProperties: false
    ApiError:
      type: object
      properties:
        error:
          type: object
          required:
            - code
            - message
          additionalProperties: false
          properties:
            code:
              type: string
              enum:
                - INVALID_REQUEST
                - ORGANIZATION_REQUIRED
                - ORGANIZATION_CHANGED
                - UNAUTHENTICATED
                - PASSWORD_REQUIRED
                - PASSWORD_INCORRECT
                - FORBIDDEN
                - ARTIFACT_NOT_FOUND
                - CONTENT_CONFLICT
                - FILE_TOO_LARGE
                - CONTENT_TOO_LARGE
                - UNSUPPORTED_FILE_TYPE
                - CONTENT_TOO_COMPLEX
                - RATE_LIMITED
                - INTERNAL_ERROR
                - STORAGE_UNAVAILABLE
                - CONVERSION_UNAVAILABLE
                - UNLOCK_UNAVAILABLE
              description: >-
                400/404 INVALID_REQUEST: The request is missing a required
                field, has the wrong shape, or failed validation. Typical causes
                are a missing file, an invalid access type, view, format, or
                artifact id, or invalid UTF-8. Unmatched non-public product
                routes also return 404 INVALID_REQUEST with message Not found.
                400 ORGANIZATION_REQUIRED: The caller asked to share with an
                organization but has no current organization membership. Upload
                with accessType org_shared and PATCH /api/v1/artifacts/{id} for
                a private artifact both use this code. 409 ORGANIZATION_CHANGED:
                PATCH /api/v1/artifacts/{id} for a private artifact named an
                organizationId that is not the caller's current membership when
                the conditional UPDATE reads, or a deadlock, serialization
                failure, or lock timeout persisted after one retry. The artifact
                is not written. That UPDATE is the linearization point: an
                authorization change that commits after its snapshot can land
                before commit and does not cancel the share. About 16 seconds is
                the budget for the two attempts, not an endpoint deadline. An
                artifact that is already org_shared returns 200 instead. 401
                UNAUTHENTICATED: The product route needs a session cookie or
                Authorization: Bearer API key, and neither was accepted. PATCH
                /api/v1/artifacts/{id} also returns this when the session was
                revoked or expired before the conditional update read it, and
                does not write. A revocation that commits after that snapshot
                does not cancel the share. 401 PASSWORD_REQUIRED: The public
                share is password protected and the request did not present a
                valid unlock grant or content ticket. 401 PASSWORD_INCORRECT:
                The password sent to the public unlock route did not match. 403
                FORBIDDEN: A cookie-authenticated write used an Origin or
                Referer that is not WEB_ORIGIN or PUBLIC_BASE_URL. 404
                ARTIFACT_NOT_FOUND: The artifact id is unknown, the caller
                cannot read it, or the public share token is not active. Rename
                and delete also use this body when the caller is not the
                creator, including an organization admin or another member who
                can read an org_shared artifact. Those cases share this body.
                409 CONTENT_CONFLICT: Another replace of the same artifact
                revision finished first. Upload again to replace the current
                content. Delete returns this code when concurrent replaces
                prevent deletion after eight attempts. Retry DELETE. 413
                FILE_TOO_LARGE: The upload is larger than the 10 MiB HTML limit.
                A JSON body larger than 102,400 bytes uses the same code with
                message The request is too large. 413 CONTENT_TOO_LARGE: Stored
                HTML is larger than the Markdown conversion input limit. 415
                UNSUPPORTED_FILE_TYPE: The upload is not a .html or .htm file.
                422 CONTENT_TOO_COMPLEX: Markdown conversion exceeded its time,
                memory, nesting, or output limit. 429 RATE_LIMITED: Too many
                public views from one client address, or too many password
                attempts. Password-attempt 429 responses include Retry-After.
                Public-view 429 responses do not. 500 INTERNAL_ERROR: An
                unexpected persistence or service failure. 503
                STORAGE_UNAVAILABLE: Object storage failed. A failed upload
                leaves no artifact row. A failed artifact delete keeps the row.
                The object may already be gone, so retry DELETE. 503
                CONVERSION_UNAVAILABLE: The Markdown conversion worker pool is
                full. This response does not include Retry-After. 503
                UNLOCK_UNAVAILABLE: Password verification is busy because the
                process-wide Argon2 gate is saturated or every remaining share
                check slot is in flight. Includes Retry-After.
            message:
              type: string
      required:
        - error
      additionalProperties: false
      description: >-
        Product error envelope. Better Auth `/api/auth` routes may return a
        different JSON shape.
  headers:
    RetryAfter:
      description: >-
        Seconds to wait before retrying. The API sets this when an
        `ApiErrorException` includes `retryAfterSeconds`: every
        password-throttle 429 RATE_LIMITED, and 503 UNLOCK_UNAVAILABLE.
        Public-view 429 and Markdown 503 CONVERSION_UNAVAILABLE do not set it.
      schema:
        type: integer
        minimum: 1

````

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