{
  "service": "observance",
  "version": "1",
  "description": "Agent-native memory API. Provides structured memory storage with types, cognitive roles, namespace scoping, relational linking, and machine-readable guidance (agent_contract) on every operational response.",
  "authentication": {
    "scheme": "Bearer",
    "header": "Authorization",
    "key_format": "obs_live_{64 hex chars}",
    "registration_endpoint": "/v1/keys/register",
    "notes": [
      "API keys are hashed with SHA-256; plaintext is never stored.",
      "Keys in query strings are rejected with 400.",
      "Each API key maps to exactly one account."
    ]
  },
  "memory_lifecycle": {
    "states": [
      "active",
      "archived",
      "deleted"
    ],
    "terminal_states": [
      "deleted"
    ],
    "transitions": [
      {
        "from": null,
        "to": "active",
        "trigger": "POST /v1/memories"
      },
      {
        "from": "active",
        "to": "archived",
        "trigger": "POST /v1/memories/:id/archive"
      },
      {
        "from": "archived",
        "to": "active",
        "trigger": "POST /v1/memories/:id/unarchive"
      },
      {
        "from": "active",
        "to": "deleted",
        "trigger": "DELETE /v1/memories/:id"
      },
      {
        "from": "archived",
        "to": "deleted",
        "trigger": "DELETE /v1/memories/:id"
      }
    ]
  },
  "memory_fields": {
    "required_at_creation": {
      "type": {
        "type": "string",
        "enum": [
          "episodic",
          "semantic",
          "procedural"
        ]
      },
      "event_at": {
        "type": "ISO 8601 timestamp",
        "description": "When the remembered event occurred"
      },
      "content": {
        "description": "At least one of content_text or content_json is required"
      }
    },
    "optional_at_creation": {
      "agent_id": {
        "type": "string",
        "format": "agt_{ULID}",
        "description": "Auto-provisioned if omitted"
      },
      "agent_name": {
        "type": "string",
        "max_length": 200
      },
      "agent_platform": {
        "type": "string",
        "max_length": 50
      },
      "namespace": {
        "type": "string",
        "default": "default",
        "pattern": "lowercase alphanumeric + hyphens, 2-100 chars"
      },
      "cognitive_role": {
        "type": "string",
        "default": "observation",
        "enum": [
          "observation",
          "reflection",
          "fact",
          "rule",
          "skill",
          "preference"
        ]
      },
      "source": {
        "type": "string",
        "default": "agent",
        "enum": [
          "agent",
          "system",
          "user",
          "derived"
        ]
      },
      "origin_type": {
        "type": "string",
        "default": "direct",
        "enum": [
          "direct",
          "summarized",
          "inferred",
          "imported"
        ]
      },
      "content_text": {
        "type": "string",
        "max_bytes": 32768
      },
      "content_json": {
        "type": "object (JSONB)",
        "max_bytes": 65536
      },
      "summary": {
        "type": "string",
        "max_length": 500
      },
      "importance_score": {
        "type": "number",
        "default": 0.5,
        "min": 0,
        "max": 1
      },
      "confidence_score": {
        "type": "number",
        "default": 1,
        "min": 0,
        "max": 1
      },
      "decay_policy": {
        "type": "string",
        "default": "exponential",
        "enum": [
          "none",
          "linear",
          "exponential"
        ]
      },
      "expires_at": {
        "type": "ISO 8601 timestamp or null",
        "default": null
      },
      "embedding_id": {
        "type": "string or null",
        "default": null
      },
      "metadata_json": {
        "type": "object (JSONB)",
        "max_bytes": 16384,
        "default": "{}"
      },
      "idempotencyKey": {
        "type": "string",
        "max_length": 255,
        "scope": "request-only, not stored on memory"
      }
    },
    "immutable_after_creation": [
      "type",
      "agent_id",
      "source",
      "origin_type",
      "event_at",
      "namespace"
    ],
    "system_managed": [
      "id",
      "status",
      "relevanceScore",
      "accessCount",
      "lastAccessedAt",
      "createdAt",
      "updatedAt"
    ]
  },
  "namespaces": {
    "description": "Organizational scoping for memories within an account.",
    "default": "default",
    "format": "Lowercase letters, digits, hyphens. 2-100 chars. Must start/end with letter or digit.",
    "cross_namespace_links": true,
    "notes": "Namespace is organizational, not a security boundary. Account isolation is the security boundary."
  },
  "agents": {
    "model": "single-agent-per-account",
    "auto_provisioning": true,
    "id_format": "agt_{ULID}",
    "description": "Each account has one agent, auto-provisioned on first memory creation. Omitting agent_id automatically uses the existing agent. No manual identity management needed.",
    "endpoints": [
      {
        "method": "GET",
        "path": "/v1/agents",
        "purpose": "List agents with memory counts"
      },
      {
        "method": "GET",
        "path": "/v1/agents/:id",
        "purpose": "Get single agent with memory count"
      },
      {
        "method": "PATCH",
        "path": "/v1/agents/:id",
        "purpose": "Update agent metadata"
      }
    ]
  },
  "links": {
    "relation_types": [
      "derived_from",
      "summarizes",
      "contradicts",
      "supports",
      "relates_to",
      "supersedes"
    ],
    "unique_constraint": "(account_id, from_memory_id, to_memory_id, relation_type)",
    "self_link_prevention": true,
    "cascade_on_delete": "Links are set to inactive when a connected memory is deleted.",
    "cascade_on_archive": "No effect. Links remain active when a memory is archived.",
    "duplicate_handling": "Duplicate creation returns the existing link (200). Inactive duplicates are reactivated."
  },
  "traversal": {
    "endpoint": "/v1/memories/:id/related",
    "depth": 1,
    "direction_filter": [
      "outgoing",
      "incoming",
      "both"
    ],
    "relation_type_filter": true,
    "deleted_memory_filter": "Deleted memories are excluded from traversal results.",
    "archived_memory_behavior": "Archived memories are included in traversal results."
  },
  "idempotency": {
    "supported_on": [
      "POST /v1/memories",
      "POST /v1/memories/batch (per-item)"
    ],
    "key_header": "Idempotency-Key (passed in request body as idempotencyKey)",
    "key_max_length": 255,
    "key_format": "Printable ASCII (0x20-0x7E)",
    "scope": "Per API key + key value",
    "ttl_days": 7,
    "stale_placeholder_recovery_seconds": 90,
    "behaviors": {
      "same_key_same_payload": "Returns cached 201 response",
      "same_key_different_payload": "409 idempotency_conflict",
      "in_flight": "503 idempotency_in_flight with retry_after_seconds: 2"
    }
  },
  "guide": {
    "endpoint": "/v1/guide",
    "customization_endpoint": "PATCH /v1/guide/hints",
    "description": "Structured operational guidance for agents. Returns Observance defaults when unauthenticated, or merged defaults + account overrides when authenticated.",
    "override_semantics": "REPLACE. PATCH /v1/guide/hints replaces the entire stored override state. The request body IS the complete desired overrides — it is not merged with previous overrides.",
    "overridable_sections": [
      "write_policy.store_when (full replacement — all 6 categories required)",
      "write_policy.avoid_storing (full array replacement)",
      "write_policy.importance (full object replacement with high/medium/low keys)"
    ],
    "non_overridable": "core_rules, reading_discipline, search_discipline, writing_discipline, linking, operations.",
    "reset": "Sending an empty body {} is rejected. To revert, send overrides matching defaults."
  },
  "rate_limits": {
    "note": "V1 uses in-memory stores (per-process). Not suitable for horizontal scaling without a shared store.",
    "limits": [
      {
        "endpoint": "POST /v1/keys/register",
        "limit": "10/hour",
        "key": "IP"
      },
      {
        "endpoint": "POST /v1/memories",
        "limit": "120/hour",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/memories/batch",
        "limit": "1 request/hour (quota counts per memory)",
        "key": "API key"
      },
      {
        "endpoint": "GET /v1/memories, GET /v1/memories/:id",
        "limit": "3000/hour",
        "key": "API key"
      },
      {
        "endpoint": "PATCH, DELETE, archive, unarchive",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/memories/:id/links",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "GET /v1/memories/:id/links, GET /v1/memories/:id/related",
        "limit": "3000/hour",
        "key": "API key"
      },
      {
        "endpoint": "DELETE /v1/links/:id",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "GET /v1/agents, GET /v1/agents/:id",
        "limit": "3000/hour",
        "key": "API key"
      },
      {
        "endpoint": "PATCH /v1/agents/:id",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "PATCH /v1/guide/hints",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "Discovery endpoints",
        "limit": "60/minute",
        "key": "IP"
      },
      {
        "endpoint": "All routes (global)",
        "limit": "6000/hour",
        "key": "IP"
      }
    ]
  },
  "endpoints": {
    "public": [
      {
        "method": "POST",
        "path": "/v1/keys/register",
        "purpose": "Register new API key"
      },
      {
        "method": "GET",
        "path": "/v1/capabilities",
        "purpose": "API capabilities document"
      },
      {
        "method": "GET",
        "path": "/v1/tool",
        "purpose": "MCP-compatible tool manifest"
      },
      {
        "method": "GET",
        "path": "/v1/schema",
        "purpose": "OpenAPI 3.1 schema document"
      },
      {
        "method": "GET",
        "path": "/.well-known/agent.json",
        "purpose": "Agent discovery manifest"
      },
      {
        "method": "GET",
        "path": "/v1/guide",
        "purpose": "Operational guide (pull-based, defaults or merged with account overrides)"
      },
      {
        "method": "GET",
        "path": "/v1/skill",
        "purpose": "Onboarding and usage guidance (structured JSON)"
      },
      {
        "method": "GET",
        "path": "/health",
        "purpose": "Health check"
      }
    ],
    "authenticated": [
      {
        "method": "POST",
        "path": "/v1/memories",
        "purpose": "Create memory"
      },
      {
        "method": "POST",
        "path": "/v1/memories/batch",
        "purpose": "Create multiple memories (partial success, per-item idempotency)"
      },
      {
        "method": "GET",
        "path": "/v1/memories",
        "purpose": "List memories (filtered, paginated, searchable via ?search=)"
      },
      {
        "method": "GET",
        "path": "/v1/memories/:id",
        "purpose": "Get single memory"
      },
      {
        "method": "PATCH",
        "path": "/v1/memories/:id",
        "purpose": "Update mutable memory fields"
      },
      {
        "method": "DELETE",
        "path": "/v1/memories/:id",
        "purpose": "Soft-delete memory"
      },
      {
        "method": "POST",
        "path": "/v1/memories/:id/archive",
        "purpose": "Archive memory"
      },
      {
        "method": "POST",
        "path": "/v1/memories/:id/unarchive",
        "purpose": "Unarchive memory"
      },
      {
        "method": "POST",
        "path": "/v1/memories/:id/links",
        "purpose": "Create memory link"
      },
      {
        "method": "GET",
        "path": "/v1/memories/:id/links",
        "purpose": "List links for a memory"
      },
      {
        "method": "DELETE",
        "path": "/v1/links/:id",
        "purpose": "Soft-delete link"
      },
      {
        "method": "GET",
        "path": "/v1/memories/:id/related",
        "purpose": "Get related memories (depth=1)"
      },
      {
        "method": "GET",
        "path": "/v1/agents",
        "purpose": "List agents"
      },
      {
        "method": "GET",
        "path": "/v1/agents/:id",
        "purpose": "Get single agent"
      },
      {
        "method": "PATCH",
        "path": "/v1/agents/:id",
        "purpose": "Update agent metadata"
      },
      {
        "method": "PATCH",
        "path": "/v1/guide/hints",
        "purpose": "Customize guide hints for this account"
      },
      {
        "method": "GET",
        "path": "/v1/billing/usage",
        "purpose": "Current usage and limits (does not count as a read)"
      },
      {
        "method": "GET",
        "path": "/v1/billing/status",
        "purpose": "Plan state and purchase history (does not count as a read)"
      },
      {
        "method": "POST",
        "path": "/v1/billing/checkout",
        "purpose": "Start plan upgrade checkout"
      },
      {
        "method": "POST",
        "path": "/v1/billing/confirm",
        "purpose": "Confirm payment and activate plan"
      }
    ]
  },
  "error_model": {
    "shape": "{ error, message, request_id, agent_contract }",
    "codes": [
      {
        "code": "missing_api_key",
        "http": 401,
        "retryable": true
      },
      {
        "code": "invalid_api_key",
        "http": 401,
        "retryable": false
      },
      {
        "code": "invalid_request",
        "http": 400,
        "retryable": false
      },
      {
        "code": "memory_not_found",
        "http": 404,
        "retryable": false
      },
      {
        "code": "agent_not_found",
        "http": 404,
        "retryable": false
      },
      {
        "code": "link_not_found",
        "http": 404,
        "retryable": false
      },
      {
        "code": "content_required",
        "http": 400,
        "retryable": false
      },
      {
        "code": "immutable_field",
        "http": 400,
        "retryable": false
      },
      {
        "code": "invalid_transition",
        "http": 409,
        "retryable": false
      },
      {
        "code": "self_link",
        "http": 400,
        "retryable": false
      },
      {
        "code": "cross_account_link",
        "http": 400,
        "retryable": false
      },
      {
        "code": "idempotency_conflict",
        "http": 409,
        "retryable": false
      },
      {
        "code": "idempotency_in_flight",
        "http": 503,
        "retryable": true
      },
      {
        "code": "quota_exhausted",
        "http": 403,
        "retryable": false
      },
      {
        "code": "active_memory_limit",
        "http": 403,
        "retryable": false
      },
      {
        "code": "agent_limit_reached",
        "http": 403,
        "retryable": false
      },
      {
        "code": "read_quota_exceeded",
        "http": 429,
        "retryable": false
      },
      {
        "code": "invalid_plan",
        "http": 400,
        "retryable": false
      },
      {
        "code": "already_on_plan",
        "http": 409,
        "retryable": false
      },
      {
        "code": "checkout_failed",
        "http": 502,
        "retryable": true
      },
      {
        "code": "purchase_not_found",
        "http": 404,
        "retryable": false
      },
      {
        "code": "purchase_expired",
        "http": 410,
        "retryable": false
      },
      {
        "code": "capture_failed",
        "http": 502,
        "retryable": true
      },
      {
        "code": "rate_limited",
        "http": 429,
        "retryable": true
      },
      {
        "code": "server_error",
        "http": 500,
        "retryable": true
      }
    ]
  },
  "agent_contract": {
    "description": "Every operational response (success and error) includes an agent_contract field with machine-readable guidance. Agents should read next_actions to determine what to do, rather than parsing HTTP status codes.",
    "version": "1",
    "structure": {
      "version": "Always \"1\"",
      "retryable": "boolean — whether the same request can be retried",
      "next_actions": "Array of Action objects, each with action, available, recommended"
    },
    "action_codes": [
      "create_memory",
      "get_memory",
      "list_memories",
      "update_memory",
      "delete_memory",
      "archive_memory",
      "unarchive_memory",
      "create_link",
      "list_links",
      "delete_link",
      "find_related",
      "check_memory_status",
      "list_agents",
      "get_agent",
      "update_agent",
      "check_usage",
      "start_checkout",
      "confirm_payment",
      "upgrade_plan",
      "retry_next_cycle",
      "retry_after_wait",
      "authenticate",
      "fix_request"
    ],
    "stability_guarantee": {
      "breaking": "Removing or renaming action codes, error codes, or agent_contract fields requires /v2/",
      "non_breaking": "Adding new optional fields, action codes, or error codes stays in /v1/"
    }
  },
  "suite": {
    "name": "agent-infrastructure-suite",
    "role": "remember",
    "model": "remember → decide → produce",
    "siblings": {
      "orchestrion": {
        "role": "decide",
        "description": "Task orchestration and work scheduling"
      },
      "outputlayer": {
        "role": "produce",
        "description": "Artifact storage and delivery"
      }
    },
    "external_refs": {
      "description": "Memories can reference external resources via metadata_json.external_refs",
      "format": {
        "service": "string",
        "resource_type": "string",
        "resource_id": "string"
      }
    }
  },
  "limitations": [
    "Rate limiting uses in-memory stores — per-process only, not distributed.",
    "Traversal depth is limited to 1 (direct neighbors only).",
    "Full-text search available via ?search= parameter on GET /v1/memories. Uses PostgreSQL websearch syntax.",
    "No vector/embedding similarity search.",
    "No webhook or push notifications — consumers must poll.",
    "Batch creation available via POST /v1/memories/batch (max 50). No bulk update/delete."
  ]
}