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

# Document Edit Links

> Share a document for external editing — no login required. Generate tokenised edit links, optionally secure them with a PIN, and let anyone with the link view or update the document's content.

Document edit links let you share a knowledge-base document as a standalone editing page. Anyone with the link can view the current content and save changes without a HelpGenie account. When a save is made, all genies that use the document are automatically updated.

**Access model:**

* **Owner actions** (`get`, `generate`, `set-pin`, `revoke`) — require a valid Bearer token. Only the document owner or an admin may manage a link.
* **Public actions** (`public-get`, `public-update`) — authenticated by the unguessable link token alone. No login required. A bearer token is accepted if present but not mandatory.

**PIN protection:** The owner can optionally set a 4–8 digit numeric PIN. Viewing is always frictionless — the PIN is checked only on Save. After 5 consecutive wrong PINs the Save action is locked for 15 minutes.

***

## Get edit link

Returns the current edit link for a document, or `null` if no link has been generated yet.

<ParamField body="resource" type="string" required>
  Must be `"doc-edit-link"`
</ParamField>

<ParamField body="action" type="string" required>
  Must be `"get"`
</ParamField>

<ParamField body="id" type="string" required>
  The document UUID.
</ParamField>

<ResponseField name="success" type="boolean">
  Whether the request succeeded.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="link" type="object | null">
      The edit link object, or `null` if no link has been generated.

      <Expandable title="properties">
        <ResponseField name="documentId" type="string">Document UUID.</ResponseField>
        <ResponseField name="token" type="string">The unguessable share token (\~43 characters).</ResponseField>
        <ResponseField name="path" type="string">App-relative path for the public editing page, e.g. `"/update/abc123..."`.</ResponseField>
        <ResponseField name="requiresPin" type="boolean">Whether a PIN must be entered to save changes.</ResponseField>
        <ResponseField name="lastUsedAt" type="string | null">ISO 8601 timestamp of the last successful save, or `null`.</ResponseField>
        <ResponseField name="createdAt" type="string">ISO 8601 creation timestamp.</ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```typescript ApiService.invoke() theme={null}
  const response = await ApiService.invoke({
    resource: "doc-edit-link",
    action: "get",
    id: "770e8400-e29b-41d4-a716-446655440002",
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.helpgenie.ai/v1 \
    -H "Authorization: Bearer hg_live_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "resource": "doc-edit-link",
      "action": "get",
      "id": "770e8400-e29b-41d4-a716-446655440002"
    }'
  ```

  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "link": {
        "documentId": "770e8400-e29b-41d4-a716-446655440002",
        "token": "abc123XYZ...",
        "path": "/update/abc123XYZ...",
        "requiresPin": false,
        "lastUsedAt": null,
        "createdAt": "2024-06-01T10:00:00.000Z"
      }
    }
  }
  ```
</CodeGroup>

***

## Generate edit link

Creates a shareable edit link for a document. Idempotent — returns the existing link unless `rotate: true` is passed, which mints a fresh token and immediately invalidates any previously shared URL.

<ParamField body="resource" type="string" required>
  Must be `"doc-edit-link"`
</ParamField>

<ParamField body="action" type="string" required>
  Must be `"generate"`
</ParamField>

<ParamField body="id" type="string" required>
  The document UUID.
</ParamField>

<ParamField body="data" type="object">
  <Expandable title="properties">
    <ParamField body="rotate" type="boolean" default="false">
      When `true`, invalidates the current token and issues a new one. Any brute-force lockout is also reset. Use this to retire a leaked or shared-too-widely link.
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="success" type="boolean">
  Whether the request succeeded.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="link" type="object">
      The edit link object (see [Get edit link](#get-edit-link) for shape).
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Returns HTTP 201 when a brand-new link is created for the first time, and HTTP 200 when returning or rotating an existing one.
</Note>

<CodeGroup>
  ```typescript ApiService.invoke() theme={null}
  // Generate (or retrieve) an edit link
  const response = await ApiService.invoke({
    resource: "doc-edit-link",
    action: "generate",
    id: "770e8400-e29b-41d4-a716-446655440002",
  });

  // Rotate the token
  const rotated = await ApiService.invoke({
    resource: "doc-edit-link",
    action: "generate",
    id: "770e8400-e29b-41d4-a716-446655440002",
    data: { rotate: true },
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.helpgenie.ai/v1 \
    -H "Authorization: Bearer hg_live_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "resource": "doc-edit-link",
      "action": "generate",
      "id": "770e8400-e29b-41d4-a716-446655440002"
    }'
  ```

  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "link": {
        "documentId": "770e8400-e29b-41d4-a716-446655440002",
        "token": "abc123XYZ...",
        "path": "/update/abc123XYZ...",
        "requiresPin": false,
        "lastUsedAt": null,
        "createdAt": "2024-06-01T10:00:00.000Z"
      }
    }
  }
  ```
</CodeGroup>

***

## Set PIN

Sets, changes, or clears the Save-gate PIN for an edit link. The PIN must be set before viewers can be required to enter it. Any change resets the brute-force lockout counter.

A link must already exist before a PIN can be set — call `generate` first.

<ParamField body="resource" type="string" required>
  Must be `"doc-edit-link"`
</ParamField>

<ParamField body="action" type="string" required>
  Must be `"set-pin"`
</ParamField>

<ParamField body="id" type="string" required>
  The document UUID.
</ParamField>

<ParamField body="data" type="object" required>
  <Expandable title="properties">
    <ParamField body="pin" type="string | null">
      A 4–8 digit numeric string to require on Save. Pass `null` or omit to remove the PIN (Save becomes frictionless).
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="success" type="boolean">
  Whether the request succeeded.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="link" type="object">
      The updated edit link object. `requiresPin` reflects the new state.
    </ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```typescript ApiService.invoke() theme={null}
  // Set a PIN
  await ApiService.invoke({
    resource: "doc-edit-link",
    action: "set-pin",
    id: "770e8400-e29b-41d4-a716-446655440002",
    data: { pin: "1234" },
  });

  // Clear the PIN
  await ApiService.invoke({
    resource: "doc-edit-link",
    action: "set-pin",
    id: "770e8400-e29b-41d4-a716-446655440002",
    data: { pin: null },
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.helpgenie.ai/v1 \
    -H "Authorization: Bearer hg_live_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "resource": "doc-edit-link",
      "action": "set-pin",
      "id": "770e8400-e29b-41d4-a716-446655440002",
      "data": { "pin": "1234" }
    }'
  ```

  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "link": {
        "documentId": "770e8400-e29b-41d4-a716-446655440002",
        "token": "abc123XYZ...",
        "path": "/update/abc123XYZ...",
        "requiresPin": true,
        "lastUsedAt": null,
        "createdAt": "2024-06-01T10:00:00.000Z"
      }
    }
  }
  ```
</CodeGroup>

***

## Revoke edit link

Permanently destroys a document's edit link. Anyone who follows the old URL immediately loses access. This is the owner's kill switch for a leaked or retired link.

<ParamField body="resource" type="string" required>
  Must be `"doc-edit-link"`
</ParamField>

<ParamField body="action" type="string" required>
  Must be `"revoke"`
</ParamField>

<ParamField body="id" type="string" required>
  The document UUID.
</ParamField>

<ResponseField name="success" type="boolean">
  `true` if the link was deleted (or did not exist).
</ResponseField>

<Warning>
  Revocation is immediate and irreversible. The shared URL stops working instantly. Generate a new link to re-enable external editing.
</Warning>

<CodeGroup>
  ```typescript ApiService.invoke() theme={null}
  await ApiService.invoke({
    resource: "doc-edit-link",
    action: "revoke",
    id: "770e8400-e29b-41d4-a716-446655440002",
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.helpgenie.ai/v1 \
    -H "Authorization: Bearer hg_live_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "resource": "doc-edit-link",
      "action": "revoke",
      "id": "770e8400-e29b-41d4-a716-446655440002"
    }'
  ```

  ```json Response theme={null}
  {
    "success": true,
    "data": { "success": true }
  }
  ```
</CodeGroup>

***

## Public — view document

Resolves a link token and returns the document's current text content so an editor can pre-fill. Viewing is always frictionless — no PIN is required to read; the PIN (if any) only gates Save.

Any invalid or revoked token returns a `404` response uniformly, so this endpoint does not reveal which tokens exist.

<Note>
  No `Authorization` header is required. This action is public and authenticated purely by the link token.
</Note>

<ParamField body="resource" type="string" required>
  Must be `"doc-edit-link"`
</ParamField>

<ParamField body="action" type="string" required>
  Must be `"public-get"`
</ParamField>

<ParamField body="data" type="object" required>
  <Expandable title="properties">
    <ParamField body="token" type="string" required>
      The link token from the edit link URL.
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="success" type="boolean">
  Whether the request succeeded.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="documentId" type="string">Document UUID.</ResponseField>
    <ResponseField name="documentName" type="string">Human-readable document name.</ResponseField>
    <ResponseField name="content" type="string">The document's current text content.</ResponseField>
    <ResponseField name="requiresPin" type="boolean">Whether a PIN must be entered before saving.</ResponseField>
    <ResponseField name="agentNames" type="string[]">Names of genies that use this document.</ResponseField>
    <ResponseField name="updatedAt" type="string | null">ISO 8601 timestamp of the last content update.</ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```typescript ApiService.invoke() theme={null}
  const response = await ApiService.invoke({
    resource: "doc-edit-link",
    action: "public-get",
    data: { token: "abc123XYZ..." },
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.helpgenie.ai/v1 \
    -H "Content-Type: application/json" \
    -d '{
      "resource": "doc-edit-link",
      "action": "public-get",
      "data": { "token": "abc123XYZ..." }
    }'
  ```

  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "documentId": "770e8400-e29b-41d4-a716-446655440002",
      "documentName": "Product FAQ",
      "content": "Q: What are your hours?\nA: Monday–Friday 9am–5pm.",
      "requiresPin": true,
      "agentNames": ["Customer Support Bot"],
      "updatedAt": "2024-06-01T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

***

## Public — save document

Saves new content for the linked document. If the link has a PIN, `pin` must match before the save is accepted. On success, all genies that use this document are automatically updated with the new content.

<Note>
  No `Authorization` header is required. This action is public and authenticated purely by the link token.
</Note>

<ParamField body="resource" type="string" required>
  Must be `"doc-edit-link"`
</ParamField>

<ParamField body="action" type="string" required>
  Must be `"public-update"`
</ParamField>

<ParamField body="data" type="object" required>
  <Expandable title="properties">
    <ParamField body="token" type="string" required>
      The link token from the edit link URL.
    </ParamField>

    <ParamField body="content" type="string" required>
      The new document content. Must be a non-empty string, maximum 1,000,000 characters.
    </ParamField>

    <ParamField body="pin" type="string">
      Required when `requiresPin` is `true`. A 4–8 digit numeric string.
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="success" type="boolean">
  `true` when the document was saved successfully.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="success" type="boolean">`true`</ResponseField>
    <ResponseField name="updatedAt" type="string">ISO 8601 timestamp of the save.</ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```typescript ApiService.invoke() theme={null}
  const response = await ApiService.invoke({
    resource: "doc-edit-link",
    action: "public-update",
    data: {
      token: "abc123XYZ...",
      content: "Updated document content...",
      pin: "1234", // only required if requiresPin is true
    },
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.helpgenie.ai/v1 \
    -H "Content-Type: application/json" \
    -d '{
      "resource": "doc-edit-link",
      "action": "public-update",
      "data": {
        "token": "abc123XYZ...",
        "content": "Updated document content...",
        "pin": "1234"
      }
    }'
  ```

  ```json Response (success) theme={null}
  {
    "success": true,
    "data": {
      "success": true,
      "updatedAt": "2024-06-01T14:30:00.000Z"
    }
  }
  ```

  ```json Response (wrong PIN) theme={null}
  {
    "success": false,
    "error": {
      "code": "FORBIDDEN",
      "message": "Incorrect PIN",
      "status": 403,
      "attemptsRemaining": 4
    }
  }
  ```
</CodeGroup>

<Warning>
  After 5 consecutive wrong PIN attempts, the Save action is locked for 15 minutes. The lockout resets automatically after the timeout or when the owner rotates the token or changes the PIN.
</Warning>

***

## Error codes

| Code                  | Status | Description                                         |
| --------------------- | ------ | --------------------------------------------------- |
| `VALIDATION_ERROR`    | 400    | Missing required parameter or invalid PIN format    |
| `FORBIDDEN`           | 403    | Wrong PIN, or you do not own the document           |
| `NOT_FOUND`           | 404    | Document or link not found / token invalid          |
| `RATE_LIMIT_EXCEEDED` | 429    | PIN lockout in effect — too many incorrect attempts |
| `INTERNAL_ERROR`      | 500    | Unexpected server error                             |
