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

# Common API tour

> Write one Memory, find it, then get a cited PreparedContext.

Use the minimum Server here. No generation model or vector search is needed.

Prove one thing you can check: write a project decision, find it, then put it into this request's PreparedContext (A bounded view for this request. It carries Citations. It is not a durable asset.). Then revise it, retire it, and confirm the history is still there.

![The first loop: write a Memory, find it, then get a cited PreparedContext.](https://mintcdn.com/powercontext/1t_ajifOEOG5YQjP/assets/basic-first-loop-en.svg?fit=max\&auto=format\&n=1t_ajifOEOG5YQjP\&q=85\&s=c80f846d7f1f5069e9a3c6aa6de6fb35)

> [!NOTE]
>
> Field names and error codes follow the latest published [official docs](https://oceanbase.github.io/powercontext/). This page helps you run the first loop.

## Four doors, the same data

HTTP, the Python Client, MCP, and the CLI all talk to the same Server. Coverage differs.

| Door | Best fit |
| - | - |
| HTTP | Cross-language service integration. Official docs own the full field list |
| Python Client | Typed async calls from Python |
| MCP | Tools an Agent calls explicitly, not the full HTTP API |
| CLI | Install, diagnose, manage the Server, and do human Review |

The local Server can also open its API notes:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
http://127.0.0.1:8000/docs
http://127.0.0.1:8000/openapi.json
```

`/docs` is a public API notes page. It still opens after you enable Server auth. `/openapi.json` and the API calls themselves need a Bearer header. Do not put a token in the URL. The full field list still follows the [official docs](https://oceanbase.github.io/powercontext/).

## Prepare the example project

The previous page installed the client extra. Create `api_tour.py` with this complete script.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import asyncio
import os
from uuid import uuid4

from powercontext.client import PowerContextClient, ServerResponseError
from powercontext.http import (
    GetMemoryEntryRequest,
    ListMemoryEntriesRequest,
    PrepareContextRequest,
    RememberMemoryRequest,
    RetireMemoryEntryRequest,
    ReviseMemoryEntryRequest,
    SearchMemoryRequest,
)


SERVER_URL = os.getenv(
    "POWERCONTEXT_CLIENT_SERVER_URL",
    "http://127.0.0.1:8000",
)
API_TOKEN = os.getenv("POWERCONTEXT_CLIENT_API_TOKEN")


async def main() -> None:
    scope_id = f"project:powercontext-101:{uuid4().hex}"

    async with PowerContextClient(
        SERVER_URL,
        token=API_TOKEN,
        timeout=10.0,
    ) as client:
        capabilities = await client.get_capabilities()
        print("search modes:", [mode.value for mode in capabilities.search_modes])
        print("scope:", scope_id)

        request = RememberMemoryRequest(
            scope_id=scope_id,
            kind="decision",
            text=(
                "Regenerate and inspect the checked-in client before "
                "running contract tests."
            ),
            reason="PowerContext 101 API tour",
        )

        remembered = await client.remember_memory(request)
        assert remembered.entry is not None
        original = remembered.entry
        print("memory:", remembered.memory)
        print("entry citation:", original.citation)
        print("source refs:", original.source_refs)

        duplicate = await client.remember_memory(request)
        assert duplicate.memory == remembered.memory
        assert duplicate.entry is None
        print("duplicate write: no-op")

        search = await client.search_memory(
            SearchMemoryRequest(
                scope_id=scope_id,
                query="What should happen before contract tests?",
                mode="auto",
                limit=5,
            )
        )
        assert search.hits
        print("used search mode:", search.mode)
        for hit in search.hits:
            print(
                "hit:",
                hit.text,
                hit.score,
                [item.value for item in hit.matched_by],
                hit.citation,
            )

        prepared = await client.prepare_context(
            PrepareContextRequest(
                scope_id=scope_id,
                query="What should happen before contract tests?",
                max_bytes=8000,
            )
        )
        assert prepared.status == "ready"
        assert prepared.content is not None
        assert len(prepared.content.encode("utf-8")) == prepared.content_bytes
        print("context schema:", prepared.schema_)
        print("context bytes:", prepared.content_bytes)
        print(prepared.content)

        exact = await client.get_memory_entry(
            GetMemoryEntryRequest(
                scope_id=scope_id,
                citation=original.citation,
            )
        )
        assert exact == original

        revised = await client.revise_memory_entry(
            ReviseMemoryEntryRequest(
                scope_id=scope_id,
                citation=original.citation,
                kind="decision",
                text=(
                    "Regenerate the checked-in client, inspect the diff, "
                    "and then run contract tests."
                ),
                reason="Add the generated-diff check",
            )
        )
        assert revised.entry is not None
        current = revised.entry
        print("revised memory:", revised.memory)
        print("current citation:", current.citation)

        historical = await client.get_memory_entry(
            GetMemoryEntryRequest(
                scope_id=scope_id,
                citation=original.citation,
            )
        )
        assert historical.text == original.text
        print("old citation remains readable")

        try:
            await client.retire_memory_entry(
                RetireMemoryEntryRequest(
                    scope_id=scope_id,
                    citation=original.citation,
                    reason="Demonstrate stale citation",
                )
            )
        except ServerResponseError as exc:
            assert exc.status_code == 409
            assert exc.code == "revision_conflict"
            print("stale citation rejected:", exc.code, exc.request_id)
        else:
            raise AssertionError("the stale citation should have conflicted")

        retired = await client.retire_memory_entry(
            RetireMemoryEntryRequest(
                scope_id=scope_id,
                citation=current.citation,
                reason="The tutorial lifecycle is complete",
            )
        )
        assert retired.entry is not None
        assert retired.entry.state == "inactive"

        active = await client.list_memory_entries(
            ListMemoryEntriesRequest(scope_id=scope_id)
        )
        assert active.entries == []

        audit = await client.list_memory_entries(
            ListMemoryEntriesRequest(
                scope_id=scope_id,
                include_inactive=True,
            )
        )
        assert len(audit.entries) == 1
        assert audit.entries[0].state == "inactive"
        print("inactive history remains readable")


if __name__ == "__main__":
    asyncio.run(main())
```

Run it:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
uv run python api_tour.py
```

Each run uses a unique Scope so old tutorial data does not break duplicate, entry-count, or Revision assertions. A real project should use a stable project Scope, not a random value.

## What this script proves

### Writing Memory does not need a Source first

`remember_memory` writes long-term knowledge you already accept. `source_refs` is normally empty in this example.

The first effective write returns:

* an exact Memory Artifact ref;
* a logical `entry_id`;
* an exact `entry_version_id`;
* current entry content and state.

Identical active content is a no-op. The response keeps the current Memory ref, but `entry` is `null`. Do not assume that every `remember_memory` response has `response.entry.citation`.

### Empty search has two normal cases

The Scope has no Memory lifecycle:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
memory = null
mode = null
hits = []
```

The Scope has Memory but the query has no match:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
memory = current exact ref
mode = fts/vector/hybrid
hits = []
```

Both are normal results under HTTP 200.

### PreparedContext is already the final envelope

`prepared.content` already contains the trust marker, boundaries, and Citations. An Agent can put it into this request as untrusted historical evidence. Do not rebuild it from search hits.

When `status="empty"`:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
content = null
content_bytes = 0
```

When `status="ready"`, the actual UTF-8 length equals `content_bytes` and does not exceed request `max_bytes`.

### Reading history and changing content use different version rules

An old Citation can still read history through `get_memory_entry`.

`revise_memory_entry` and `retire_memory_entry` accept only the current-head Citation. After a Memory change moves the Revision forward, changing with the old Citation returns HTTP 409 `revision_conflict`.

Use the new Citation from the change response.

### Retire does not delete

After retirement:

* default listing no longer includes the entry;
* active search and recall stop selecting it;
* `include_inactive=True` still supports audit;
* exact historical read still works.

## What to read when it fails

The Python Client keeps the HTTP status, stable error code, Server message, and `X-PowerContext-Request-ID`.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from powercontext.client import (
    InvalidResponseError,
    ServerResponseError,
    TransportError,
)

try:
    ...
except ServerResponseError as exc:
    print(exc.status_code, exc.code, exc.server_message, exc.request_id)
except TransportError as exc:
    print("HTTP transport failed:", exc)
except InvalidResponseError as exc:
    print("Response violated the generated contract:", exc)
```

Common statuses:

| HTTP | Meaning |
| - | - |
| 401 | Bearer token is missing or invalid |
| 404 | The exact immutable value does not exist |
| 409 | Current head, Citation, or expected version conflict |
| 422 | The request or capability violates the contract |
| 503 | A required Runtime binding or dependency is unavailable |

After a 409, read the current value before you decide whether to retry. Do not guess the Revision.

## MCP and HTTP do not cover the same surface

MCP is the tool set an Agent calls explicitly. HTTP is the full API.

`POST /v1/context/prepare` and Python Client `prepare_context` are not MCP tools. A hook or framework adapter usually prepares the final PreparedContext at request time. MCP also does not reproduce each integration's automatic recall, capture, and flush.

To connect a client and see which tools you can call, open [Connect with MCP](https://powercontext.mintlify.site/en/basic-usage/mcp.md).

## How you know it worked

The terminal should show:

* The first write returns an exact entry Citation.
* A duplicate write is a no-op with `entry=None`.
* Full-text search / `auto` finds the active entry.
* PreparedContext is `ready`, and the byte count matches.
* After revise, the old Citation still reads exactly.
* Changing with the old Citation returns 409.
* After retire, the default list is empty. Inactive history is still readable.

If this does not match, start with [Troubleshooting](https://powercontext.mintlify.site/en/start/troubleshooting.md).

## Next step

> [card] [Check the counts in Dashboard](https://powercontext.mintlify.site/en/basic-usage/dashboard.md)
>
> The Dashboard does not show Memory text. Configure the Scope you just used and check `total=1`, `active=0`, and `inactive=1`.

> [card] [Source and Memory](https://powercontext.mintlify.site/en/basic-usage/source-and-memory.md)
>
> The next path captures a Source first, then extracts Memory. A successful capture does not mean knowledge stayed.


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