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

# Restore a forgotten node

> Restore a node forgotten within the last 30 days.

What comes back: the node's graph position, title, metadata and authority.
What does not: its content. Forget is an erasure verb, so it deleted the
node's text outright and nothing can undo that. Edges removed around the
node stay removed. The restored node is therefore visible and linkable but
contributes no retrievable text until you write new content to it with
`PATCH /v1/graph/nodes/{id}`.

Past the 30-day undelete window a tombstone is gone by contract, so this
returns exactly the same 404 as a node that never existed, whether or not
the garbage collector has physically removed the row yet. Restoring a node
that is not tombstoned is a no-op success (`restored: false`), so a
retried undelete is always safe. A node not owned by the effective subject
is 404.

## Authorization

This route requires the `graph:write` scope. A key carrying `memory:forget` or `write` also qualifies.

A missing or invalid credential returns 401 [`authentication_error`](/errors/authentication_error). A valid credential without the scope returns 403 [`permission_denied`](/errors/permission_denied), and the problem body names the exact scope required.

## Headers

These are request conventions the contract does not declare as parameters, so they do not appear in the schema tables below.

| Header            | Applies  | What it does                                                                                                                                           |
| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `X-Exo-API-Key`   | Required | Your Exo API key, `exo_<env>_<token>`. `Authorization: Bearer exo_...` is accepted as an alternate.                                                    |
| `X-Exo-Subject`   | Optional | Acts for a provisioned subject instead of the key owner. An unprovisioned id returns 403 [`subject_not_provisioned`](/errors/subject_not_provisioned). |
| `Idempotency-Key` | Honoured | Makes a retry safe. The same key with the same body replays the original response instead of acting twice. See [Idempotency](/platform/idempotency).   |
| `X-Request-Id`    | Response | Returned on every response, including `204`s. Quote it in a support request.                                                                           |

## Success responses

| Status | Meaning             |
| ------ | ------------------- |
| `200`  | Successful Response |

## Errors

| Status | Code                                                   | When                                                           |
| ------ | ------------------------------------------------------ | -------------------------------------------------------------- |
| `400`  | [`invalid_request`](/errors/invalid_request)           | Malformed request, for example an invalid cursor.              |
| `401`  | [`authentication_error`](/errors/authentication_error) | Missing or invalid credentials.                                |
| `403`  | [`permission_denied`](/errors/permission_denied)       | Missing scope, or an unprovisioned subject selector.           |
| `404`  | [`not_found`](/errors/not_found)                       | The resource does not exist, or is not visible to this caller. |
| `409`  | [`conflict`](/errors/conflict)                         | A conflict with the current state of the resource.             |
| `422`  | [`validation_error`](/errors/validation_error)         | The request was understood but failed field validation.        |
| `429`  | [`rate_limit_exceeded`](/errors/rate_limit_exceeded)   | Rate limit exceeded. Retry after the Retry-After interval.     |
| `503`  | [`service_degraded`](/errors/service_degraded)         | A dependency is temporarily unavailable. Safe to retry.        |

Beyond the shared statuses, this route can answer with [`idempotency_in_flight`](/errors/idempotency_in_flight), [`idempotency_key_reuse`](/errors/idempotency_key_reuse). Every problem body names its own `code`, and the `type` URI always resolves to the matching page.

Every error is an RFC 9457 `application/problem+json` body carrying a stable `code`, a `requestId`, and a `suggestedAction` where Exo has one. See [Errors](/platform/errors).

## Notes

* **Inside the 30-day window only.** After the tombstone is purged there is nothing left to restore.
* **`contentRestored` is false.** Graph position and metadata come back. The erased chunks do not.


## OpenAPI

````yaml openapi.json POST /v1/graph/nodes/{node_id}/undelete
openapi: 3.1.0
info:
  contact:
    name: Exo
    url: https://get-exo.com/
  description: >-
    The Exo public developer API: ingest content, retrieve identity-conditioned
    context, and read the knowledge graph. Authenticate every request with an
    Exo API key, either as the X-Exo-API-Key header or as Authorization: Bearer
    exo_... . Errors follow RFC 9457 problem+json with a stable snake_case code
    on every body.
  title: Exo API
  version: 1.0.0
servers:
  - url: https://api.get-exo.com
security:
  - ApiKeyHeader: []
  - BearerAuth: []
tags:
  - name: retrieve
    x-group: Retrieval
  - name: search
    x-group: Search
  - name: ingest
    x-group: Ingestion
  - name: jobs
    x-group: Jobs
  - name: subjects
    x-group: Subjects
  - name: recall
    x-group: Recall
  - name: graph
    x-group: Graph
  - name: insights
    x-group: Contradictions and proposals
  - name: condition
    x-group: Condition
  - name: brain
    x-group: Brain
  - name: sessions
    x-group: Coding sessions
  - name: connectors
    x-group: Connectors
  - name: events
    x-group: Events and webhooks
  - name: meta
    x-group: Identity
  - name: keys
    x-group: Keys and sandbox
  - name: usage
    x-group: Usage
  - name: settings
    x-group: Settings
  - name: exports
    x-group: Exports
  - name: team
    x-group: Team
  - name: admin
    x-group: Account and purge
paths:
  /v1/graph/nodes/{node_id}/undelete:
    post:
      tags:
        - graph
      summary: Restore a forgotten node
      description: >-
        Restore a node forgotten within the last 30 days.


        What comes back: the node's graph position, title, metadata and
        authority.

        What does not: its content. Forget is an erasure verb, so it deleted the

        node's text outright and nothing can undo that. Edges removed around the

        node stay removed. The restored node is therefore visible and linkable
        but

        contributes no retrievable text until you write new content to it with

        PATCH /v1/graph/nodes/{id}.


        Past the 30-day undelete window a tombstone is gone by contract, so this

        returns exactly the same 404 as a node that never existed, whether or
        not

        the garbage collector has physically removed the row yet. Restoring a
        node

        that is not tombstoned is a no-op success (``restored: false``), so a

        retried undelete is always safe. A node not owned by the effective
        subject

        is 404.


        Scope: ``graph:write`` (a ``write`` key also qualifies). Restoring is a

        structural repair, not an erasure, so it does not need
        ``memory:forget``.
      operationId: undeleteNode
      parameters:
        - description: The knowledge-graph node id.
          in: path
          name: node_id
          required: true
          schema:
            description: The knowledge-graph node id.
            title: Node Id
            type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UndeleteResponse'
          description: Successful Response
        '400':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Malformed request, for example an invalid cursor.
        '401':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Missing or invalid credentials
        '403':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Missing scope or unprovisioned subject
        '404':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: No such node owned by the effective subject
        '409':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Conflict, for example a duplicate in-flight idempotent request.
        '422':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Validation Error
        '429':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: Rate limit exceeded
        '503':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
          description: A dependency is unavailable
components:
  schemas:
    UndeleteResponse:
      description: >-
        Result of restoring a forgotten node.


        ``restored`` is true when this call cleared a tombstone and false when
        the

        node was already live (a no-op success). ``contentRestored`` is ALWAYS

        false: forget erases the node's text, and no undelete can bring it back.

        ``deletedAt`` is the tombstone this call cleared, null on the no-op.
      properties:
        contentRestored:
          default: false
          title: Contentrestored
          type: boolean
        deletedAt:
          anyOf:
            - format: date-time
              type: string
            - type: 'null'
          title: Deletedat
        id:
          title: Id
          type: string
        restored:
          title: Restored
          type: boolean
        undeleteWindowDays:
          default: 30
          title: Undeletewindowdays
          type: integer
      required:
        - id
        - restored
      title: UndeleteResponse
      type: object
    Problem:
      description: |-
        RFC 9457 problem+json envelope (spec D9), documented in OpenAPI.

        Invariant: ``code`` equals the tail of the ``type`` URI
        (``https://docs.get-exo.com/errors/<code>``).
      properties:
        code:
          title: Code
          type: string
        detail:
          title: Detail
          type: string
        documentationUrl:
          anyOf:
            - type: string
            - type: 'null'
          title: Documentationurl
        errors:
          anyOf:
            - items:
                $ref: '#/components/schemas/FieldViolation'
              type: array
            - type: 'null'
          title: Errors
        requestId:
          default: ''
          title: Requestid
          type: string
        status:
          title: Status
          type: integer
        suggestedAction:
          anyOf:
            - type: string
            - type: 'null'
          title: Suggestedaction
        title:
          title: Title
          type: string
        type:
          title: Type
          type: string
      required:
        - type
        - title
        - status
        - detail
        - code
      title: Problem
      type: object
    FieldViolation:
      description: A single field-level violation inside a Problem ``errors[]`` list.
      properties:
        code:
          title: Code
          type: string
        field:
          title: Field
          type: string
        message:
          title: Message
          type: string
        pointer:
          title: Pointer
          type: string
      required:
        - pointer
        - field
        - code
        - message
      title: FieldViolation
      type: object
  securitySchemes:
    ApiKeyHeader:
      description: An Exo API key (exo_...) sent as the X-Exo-API-Key header.
      in: header
      name: X-Exo-API-Key
      type: apiKey
    BearerAuth:
      description: The same Exo API key (exo_...) sent as an Authorization bearer token.
      scheme: bearer
      type: http

````