{
  "openapi": "3.0.0",
  "info": {
    "title": "Octopia Knowledge Graph API",
    "description": "API du Knowledge Graph Octopia.\n\nDeux surfaces :\n\n- **Ingestion connecteurs** (`POST /octopia/api/ingest`) : les connecteurs\n  externes poussent leurs données dans le graphe. Auth par clé API via\n  l'en-tête `X-Connector-Key`.\n- **Cerveau d'entreprise** (`/api/v1/octopia/memories`) : mémoire sémantique\n  partagée entre devs et agents (fait/préférence/contexte/insight),\n  recherchable sémantiquement et rattachée aux entités du graphe (client,\n  projet…). Auth OAuth2 `Bearer` (PAT, ex. via le device flow CLI) avec le\n  scope adapté au verbe (`read` pour les lectures — y compris la recherche\n  exposée en `POST /memories/search` — `write` pour les mutations).\n\nPour l'authentification, voir la page [Authentification](../authentication/).\n",
    "version": "1.1.0",
    "contact": {
      "name": "API Support",
      "email": "support@sinoia.fr"
    }
  },
  "servers": [
    {
      "url": "http://localhost:3000",
      "description": "Serveur de développement"
    },
    {
      "url": "https://rec.hubdoc.sinoia.cloud",
      "description": "Serveur de recette"
    }
  ],
  "security": [
    {
      "ConnectorApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Octopia",
      "description": "Octopia Knowledge Graph API — ingestion de données externes via connecteurs"
    },
    {
      "name": "Octopia — Mémoire",
      "x-displayName": "Mémoire",
      "description": "Cerveau d'entreprise : mémoire sémantique partagée entre devs et agents.\nChaque mémoire porte son auteur (`user_id`), est recherchable\nsémantiquement par tous, et se rattache à des entités du graphe (client,\nprojet…) via `links` — ces entités sont les points de convergence\n(documents hubdoc, mémoires…) qui rendent le graphe naviguable.\n"
    }
  ],
  "paths": {
    "/octopia/api/ingest": {
      "post": {
        "summary": "Ingérer des données externes dans le Knowledge Graph",
        "description": "Importe des entités et des relations dans le Knowledge Graph Octopia depuis un connecteur externe.\n\n**Authentification** : via header `X-Connector-Key` (clé API du connecteur, pas OAuth2).\n\n**Workflow typique** :\n1. Créer un connecteur via l'interface Octopia (qui génère une clé API)\n2. Envoyer les données à cet endpoint avec la clé dans le header\n3. Les entités sont créées ou mises à jour (upsert par nom)\n4. Les relations sont créées entre les entités\n\n**Types d'entités supportés** : person, company, team, skill, tag, project, contract, product, service, location, event, role\n\n**Cas d'usage** :\n- Synchronisation depuis un CRM (Salesforce, HubSpot)\n- Import depuis un ERP (SAP, Sage)\n- Enrichissement depuis des sources externes (API, CSV)\n",
        "tags": [
          "Octopia"
        ],
        "security": [
          {
            "ConnectorApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OctopiaIngestionRequest"
              },
              "examples": {
                "complete": {
                  "summary": "Import complet (entités + relations)",
                  "value": {
                    "entities": [
                      {
                        "type": "company",
                        "name": "ACME Corporation",
                        "attributes": {
                          "industry": "technologie",
                          "city": "Paris",
                          "country": "FR"
                        }
                      },
                      {
                        "type": "person",
                        "name": "Jean Dupont",
                        "attributes": {
                          "email": "jean.dupont@acme.fr"
                        }
                      },
                      {
                        "type": "project",
                        "name": "Migration Cloud 2025",
                        "attributes": {
                          "status": "active"
                        }
                      }
                    ],
                    "relations": [
                      {
                        "from_type": "person",
                        "from_name": "Jean Dupont",
                        "to_type": "company",
                        "to_name": "ACME Corporation",
                        "edge_type": "works_in"
                      },
                      {
                        "from_type": "person",
                        "from_name": "Jean Dupont",
                        "to_type": "project",
                        "to_name": "Migration Cloud 2025",
                        "edge_type": "works_on_project"
                      }
                    ]
                  }
                },
                "minimal": {
                  "summary": "Import minimal (entités seules)",
                  "value": {
                    "entities": [
                      {
                        "type": "company",
                        "name": "Société XYZ"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ingestion réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OctopiaIngestionResponse"
                },
                "examples": {
                  "success": {
                    "summary": "Import réussi",
                    "value": {
                      "imported": 3,
                      "updated": 0,
                      "errors": []
                    }
                  },
                  "partial": {
                    "summary": "Import partiel avec erreurs",
                    "value": {
                      "imported": 2,
                      "updated": 1,
                      "errors": [
                        "Unknown entity type: unknown_type",
                        "Cannot create relation works_in: missing nodes"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé de connecteur invalide ou manquante",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "unauthorized",
                  "message": "Invalid or missing connector key"
                }
              }
            }
          },
          "422": {
            "description": "Erreur lors de l'ingestion (échec de l'interactor)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "ingestion_error",
                  "message": "Connector is not active"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/octopia/memories": {
      "get": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Lister les mémoires du cerveau d'entreprise",
        "operationId": "listMemories",
        "tags": [
          "Octopia — Mémoire"
        ],
        "parameters": [
          {
            "name": "memory_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "fact",
                "preference",
                "context",
                "insight"
              ]
            }
          },
          {
            "name": "scope[type]",
            "in": "query",
            "required": false,
            "description": "Type d'entité de rattachement (ex. `client`, `project`) — restreint la liste.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope[name]",
            "in": "query",
            "required": false,
            "description": "Nom de l'entité de rattachement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Per-Page": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Has-More": {
                "description": "Page suivante disponible",
                "schema": {
                  "type": "boolean"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Memory"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Stocker une mémoire et la rattacher à des entités du graphe",
        "operationId": "storeMemory",
        "tags": [
          "Octopia — Mémoire"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Mémoire stockée (avec les entités effectivement rattachées).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Memory"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "links": {
                          "type": "array",
                          "description": "Entités effectivement rattachées.",
                          "items": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/EntityLink"
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "description": "Clé du nœud dans le graphe."
                                  }
                                }
                              }
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/octopia/memories/{id}": {
      "get": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Détail d'une mémoire (avec ses entités liées)",
        "operationId": "showMemory",
        "tags": [
          "Octopia — Mémoire"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Memory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Oublier une mémoire (supprime le record et son nœud graphe)",
        "operationId": "forgetMemory",
        "tags": [
          "Octopia — Mémoire"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Mémoire oubliée."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/octopia/memories/search": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Recherche sémantique dans le cerveau d'entreprise",
        "description": "Recherche sémantique (pgvector) sur **tous les auteurs** (mémoire commune),\nenrichie des entités liées du graphe. Optionnellement restreinte à une\nentité via `scope`.\n",
        "operationId": "searchMemories",
        "tags": [
          "Octopia — Mémoire"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemorySearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemorySearchResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "OctopiaIngestionRequest": {
        "type": "object",
        "description": "Données à ingérer dans le Knowledge Graph Octopia",
        "required": [
          "entities"
        ],
        "properties": {
          "entities": {
            "type": "array",
            "description": "Liste des entités à créer ou mettre à jour (upsert par nom)",
            "items": {
              "type": "object",
              "required": [
                "type",
                "name"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "description": "Type de l'entité",
                  "enum": [
                    "person",
                    "company",
                    "team",
                    "skill",
                    "tag",
                    "project",
                    "contract",
                    "product",
                    "service",
                    "location",
                    "event",
                    "role"
                  ]
                },
                "name": {
                  "type": "string",
                  "description": "Nom de l'entité (utilisé comme clé d'upsert)"
                },
                "attributes": {
                  "type": "object",
                  "description": "Attributs supplémentaires de l'entité (dépendent du type)",
                  "additionalProperties": true
                }
              }
            }
          },
          "relations": {
            "type": "array",
            "description": "Liste des relations à créer entre entités",
            "items": {
              "type": "object",
              "required": [
                "from_type",
                "from_name",
                "to_type",
                "to_name",
                "edge_type"
              ],
              "properties": {
                "from_type": {
                  "type": "string",
                  "description": "Type de l'entité source",
                  "enum": [
                    "person",
                    "company",
                    "team",
                    "skill",
                    "tag",
                    "project",
                    "contract",
                    "product",
                    "service",
                    "location",
                    "event",
                    "role"
                  ]
                },
                "from_name": {
                  "type": "string",
                  "description": "Nom de l'entité source"
                },
                "to_type": {
                  "type": "string",
                  "description": "Type de l'entité cible",
                  "enum": [
                    "person",
                    "company",
                    "team",
                    "skill",
                    "tag",
                    "project",
                    "contract",
                    "product",
                    "service",
                    "location",
                    "event",
                    "role"
                  ]
                },
                "to_name": {
                  "type": "string",
                  "description": "Nom de l'entité cible"
                },
                "edge_type": {
                  "type": "string",
                  "description": "Type de la relation (ex: works_in, member_of, has_skill, related_to, etc.)\n"
                }
              }
            }
          }
        }
      },
      "OctopiaIngestionResponse": {
        "type": "object",
        "description": "Résultat de l'ingestion dans le Knowledge Graph",
        "properties": {
          "imported": {
            "type": "integer",
            "description": "Nombre d'entités créées"
          },
          "updated": {
            "type": "integer",
            "description": "Nombre d'entités mises à jour"
          },
          "errors": {
            "type": "array",
            "description": "Liste des erreurs rencontrées pendant l'ingestion",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Memory": {
        "type": "object",
        "description": "Mémoire sémantique du cerveau d'entreprise (partagée entre devs et agents).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string",
            "description": "Fait atomique en langage naturel."
          },
          "memory_type": {
            "type": "string",
            "enum": [
              "fact",
              "preference",
              "context",
              "insight"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "professional",
              "personal",
              "project",
              "domain",
              "technical"
            ],
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "format": "float",
            "description": "0.0–1.0."
          },
          "salience": {
            "type": "number",
            "format": "float"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "origin": {
            "type": "string",
            "description": "Provenance de la mémoire (ex. `octopia_api`, `openmemory`)."
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "Auteur de la mémoire (utilisateur du token)."
          },
          "similarity": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Score de similarité 0..1 (présent uniquement dans les résultats de recherche)."
          },
          "related_entities": {
            "type": "array",
            "nullable": true,
            "description": "Entités du graphe liées (lecture unitaire et recherche).",
            "items": {
              "$ref": "#/components/schemas/RelatedEntity"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemoryMutation": {
        "type": "object",
        "description": "Payload de stockage d'une mémoire.",
        "required": [
          "content"
        ],
        "properties": {
          "content": {
            "type": "string",
            "minLength": 30,
            "description": "Fait atomique en langage naturel (≥ 30 caractères ; un contenu trop court ou bruité est rejeté par le quality gate en `422`).",
            "example": "Le client DSH utilise l'ERP Aareon Prem'Habitat pour ses signalements."
          },
          "memory_type": {
            "type": "string",
            "enum": [
              "fact",
              "preference",
              "context",
              "insight"
            ],
            "default": "context"
          },
          "category": {
            "type": "string",
            "enum": [
              "professional",
              "personal",
              "project",
              "domain",
              "technical"
            ]
          },
          "confidence": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 1,
            "default": 0.8
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "array",
            "description": "Entités du graphe auxquelles rattacher la mémoire (client, projet…).",
            "items": {
              "$ref": "#/components/schemas/EntityLink"
            }
          }
        }
      },
      "EntityLink": {
        "type": "object",
        "description": "Lien vers une entité du graphe de connaissances (client, projet…). C'est le\n\"scope\" de la mémoire : le namespace n'est pas une chaîne libre mais une\nentité à laquelle la mémoire se rattache (arête `about`). Entité créée à la\nvolée si absente.\n",
        "required": [
          "type",
          "name"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Type d'entité : `client`/`company`, `project`, `person`, `team`, `skill`, `tag`, `contract`, `product`, `service`, `location`, `event`, `role`.",
            "example": "client"
          },
          "name": {
            "type": "string",
            "description": "Nom de l'entité.",
            "example": "DSH"
          }
        }
      },
      "RelatedEntity": {
        "type": "object",
        "description": "Entité du graphe liée à une mémoire (renvoyée à la lecture/recherche).",
        "properties": {
          "type": {
            "type": "string",
            "description": "Type d'entité (collection du graphe, au singulier).",
            "example": "company"
          },
          "label": {
            "type": "string",
            "description": "Libellé de l'entité.",
            "example": "DSH"
          }
        }
      },
      "MemorySearchRequest": {
        "type": "object",
        "description": "Requête de recherche sémantique.",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "Texte de recherche en langage naturel.",
            "example": "quel ERP utilise DSH ?"
          },
          "memory_type": {
            "type": "string",
            "enum": [
              "fact",
              "preference",
              "context",
              "insight"
            ]
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 5
          },
          "scope": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EntityLink"
              }
            ],
            "description": "Restreint la recherche aux mémoires liées à cette entité."
          }
        }
      },
      "MemorySearchResponse": {
        "type": "object",
        "description": "Résultats de recherche, triés par similarité décroissante.",
        "properties": {
          "query": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Memory"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Type d'erreur",
            "example": "validation_error"
          },
          "message": {
            "type": "string",
            "description": "Message d'erreur lisible",
            "example": "The request contains invalid parameters"
          },
          "details": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Détails supplémentaires de l'erreur",
            "example": [
              "Name is required",
              "Workspace ID must be a valid integer"
            ]
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Requête incorrecte - paramètres invalides",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "bad_request",
              "message": "Paramètres de requête invalides"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentification requise",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "unauthorized",
              "message": "Authentification requise"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Accès refusé - permissions insuffisantes",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "forbidden",
              "message": "Accès refusé"
            }
          }
        }
      },
      "NotFound": {
        "description": "Ressource introuvable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "not_found",
              "message": "Ressource introuvable"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Erreurs de validation",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "validation_error",
              "message": "La validation a échoué",
              "details": [
                "Le nom est requis",
                "L'identifiant d'espace de travail doit être valide"
              ]
            }
          }
        }
      },
      "InternalError": {
        "description": "Erreur interne du serveur",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "internal_error",
              "message": "Une erreur inattendue s'est produite"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ConnectorApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Connector-Key",
        "description": "Clé API d'un connecteur Octopia (générée lors de la création du connecteur)"
      },
      "OAuth2Password": {
        "type": "oauth2",
        "description": "Flow password pour applications de confiance (mobile, CLI, tests)",
        "flows": {
          "password": {
            "tokenUrl": "/oauth/token",
            "scopes": {
              "read": "Lecture des ressources",
              "write": "Modification des ressources",
              "admin": "Accès administratif"
            }
          }
        }
      },
      "OAuth2AuthCode": {
        "type": "oauth2",
        "description": "Flow authorization code pour applications frontend/tierce",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "/oauth/authorize",
            "tokenUrl": "/oauth/token",
            "scopes": {
              "read": "Lecture des ressources",
              "write": "Modification des ressources",
              "admin": "Accès administratif"
            }
          }
        }
      }
    },
    "parameters": {
      "IdParameter": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identifiant de la ressource",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
      }
    }
  }
}