For Coding Agents¶
Dense, prescriptive reference for AI agents integrating with the iscc-web REST API or working on
the codebase. Tables and code over prose. Terminology matches the codebase exactly.
Integrating with the API¶
Base path: /api/v1 (public instance: https://web.iscc.io/api/v1). The OpenAPI spec is served at
/docs/openapi.yaml with interactive docs at /docs. Full details:
REST API reference.
Endpoints¶
| Method | Path | Purpose | Success |
|---|---|---|---|
POST |
/iscc?semantic=&granular= |
Upload file and generate its ISCC | 201 |
GET |
/iscc/{media_id} |
Stored ISCC result for a previous upload | 200 |
POST |
/media |
Upload file without ISCC processing | 201 |
GET |
/media/{media_id} |
Download uploaded file (uploader-only*) | 200 |
DELETE |
/media/{media_id} |
Delete media package (uploader-only*) | 204 |
GET |
/metadata/{media_id} |
Extract embedded metadata | 200 |
POST |
/metadata/{media_id} |
Embed metadata into a copy + re-ISCC (uploader-only*) | 201 |
GET |
/explain/{iscc} |
Decompose an ISCC into its units | 200 |
POST |
/simprint |
Granular simprints from plain text (JSON body) | 200 |
* With ISCC_WEB_PRIVATE_FILES=true (the default) these return 403 unless the request comes
from the original uploader's IP (identified by its blake3 hash — no raw IPs are stored).
Path parameter handling:
| Parameter | Validation | On failure |
|---|---|---|
media_id |
Routing layer: {mid:media_id} pattern [a-v0-9]{13}$ (lowercased Flake code) |
404 |
iscc |
Handler: ic.iscc_normalize + ic.iscc_validate (ISCC: prefix optional) |
400 |
The iscc entry in Route.value_patterns (iscc_web/main.py) is registered but unused — the
explain route declares a bare {iscc} parameter, so no routing-level pattern applies to it.
Upload contract¶
Uploads are raw request bodies — not multipart forms. The filename travels base64-encoded in
the X-Upload-Filename header. Applies to POST /iscc and POST /media.
| Rule | Detail |
|---|---|
| Body | Raw file bytes (application/octet-stream semantics) |
X-Upload-Filename |
Required. Base64 of the UTF-8 filename. Missing, invalid base64, or invalid UTF-8 → 400 |
Content-Type |
Optional. Stored as upload metadata and replayed on download |
Content-Length |
Must be ≥ 1 and ≤ ISCC_WEB_MAX_UPLOAD_SIZE (default 1 GB), else 400 |
| Response | 201 with Location: /api/v1/media/{media_id} and an ISCC metadata JSON body |
import base64
import httpx
filename = "your-media-file.jpg"
headers = {
"X-Upload-Filename": base64.b64encode(filename.encode("utf-8")).decode("ascii"),
"Content-Type": "image/jpeg",
}
with open(filename, "rb") as f:
response = httpx.post(
"https://web.iscc.io/api/v1/iscc",
params={"semantic": "false", "granular": "true"},
content=f.read(), # raw body, NOT files={...}
headers=headers,
timeout=None,
)
response.raise_for_status()
result = response.json()
print(result["iscc"], response.headers["location"])
API behaviors to plan around¶
semantic/granularomitted onPOST /iscc→ service defaults apply (semantic off, granular on, viaISCC_SDK_EXPERIMENTAL/ISCC_SDK_GRANULAR); explicit values override per request.semantic=trueyields a 5-unit ISCC-CODE that is not a standard ISO 24138 identifier.POST /metadata/{media_id}has nosemanticparam — it reprocesses with service defaults, so embedding into a 5-unit result returns a 4-unit code.- Unsupported media types do not fail: they yield a 2-unit wide ISCC-SUM (Data + Instance)
because
ISCC_SDK_FALLBACK=trueis the service default. - Uploads are ephemeral: a cleanup task deletes media packages older than
ISCC_WEB_STORAGE_EXPIRY(default 3600 s). Persist the201response JSON. - CORS is disabled by default; set
ISCC_WEB_CORS_ORIGINSfor cross-origin clients (see Configuration).
Working on the codebase¶
Architecture map¶
| File | Contains |
|---|---|
iscc_web/__init__.py |
Star imports that register all controllers as an import side effect — routing depends on this |
iscc_web/main.py |
BlackSheep Application, CORS setup, static file serving, Route.value_patterns (mid, iscc), cleanup-task startup, Pool shutdown, uvicorn main() |
iscc_web/asgi.py |
ASGI entrypoint for the uvicorn dev server |
iscc_web/options.py |
IsccWebOptions (pydantic-settings, ISCC_WEB_ prefix); ISCC_LIB_ENV_DEFAULTS applied via os.environ.setdefault before the iscc libraries are imported |
iscc_web/cleanup.py |
cleanup_task() — deletes expired media packages; skips 061knt35ejv6o and .gitignore |
iscc_web/vite.py |
Jinja2 tags {% vite_hmr_client %} / {% vite_asset %}; Vite manifest resolution in production |
iscc_web/api/iscc.py |
Iscc controller — POST /iscc, GET /iscc/{media_id} |
iscc_web/api/media.py |
Media controller — POST /media, GET/DELETE /media/{media_id} |
iscc_web/api/metadata.py |
Metadata controller — GET/POST /metadata/{media_id} |
iscc_web/api/explain.py |
Explain controller — GET /explain/{iscc} (with iscc-core IndexError workaround) |
iscc_web/api/simprint.py |
Simprint controller — POST /simprint; text_simprints() (byte-identical to iscc-search) |
iscc_web/api/mixins.py |
FileHandler — shared upload/storage logic; code_iscc() pool wrapper |
iscc_web/api/pool.py |
Pool — ProcessPoolExecutor wrapper, self-healing on BrokenProcessPool |
iscc_web/api/schema.py |
Generated pydantic models from openapi.yaml — never edit by hand |
iscc_web/api/models.py |
UploadMeta — internal upload metadata model (hand-written) |
iscc_web/api/common.py |
rmtree, base_url helpers |
iscc_web/static/docs/openapi.yaml |
API source of truth; served at /docs |
iscc_web/templates/index.jinja |
Frontend host page (Vite asset tags) |
frontend/ |
Vue 3 + TypeScript demo SPA; the API boundary is frontend/services/api.service.ts |
tests/conftest.py |
Session-scoped uvicorn server subprocess (port 44555 + xdist worker offset) |
Commands¶
Backend (uv; Python version pinned in .python-version):
| Command | Purpose |
|---|---|
uv sync |
Install backend dependencies into .venv |
uv run iscc-web |
Dev server at http://localhost:8000 (auto-reload) |
uv run poe all |
formatopenapi + codegen + format + test + check-complexity — run before committing |
uv run poe test |
pytest -n auto with the 100% branch-coverage gate |
uv run pytest tests/test_api_iscc.py::test_get_iscc_ok |
Single test (no xdist, no coverage) |
uv run poe codegen |
Regenerate iscc_web/api/schema.py from openapi.yaml |
uv run poe formatopenapi |
Reformat openapi.yaml |
uv run poe format |
ruff format (line-length 120, LF line endings) |
uv run prek run --all-files |
Pre-commit hooks (ruff, mdformat, openapi-spec-validator, ...) |
Frontend (pnpm; Node version pinned in .tool-versions):
| Command | Purpose |
|---|---|
pnpm install |
Install frontend dependencies |
pnpm run dev |
Vite dev server on port 5173 |
pnpm run build |
vue-tsc type check + build into iscc_web/static/dist/ |
pnpm run test |
Vitest unit/component tests |
pnpm exec eslint frontend/ |
Lint (CI parity) |
pnpm exec vue-tsc --noEmit |
Type check (CI parity) |
Environment variables¶
All service options use the ISCC_WEB_ prefix (iscc_web/options.py). Quick table — full
reference in Configuration:
| Variable | Default | Purpose |
|---|---|---|
ISCC_WEB_ENVIRONMENT |
development |
development enables debug details + Vite dev-server assets |
ISCC_WEB_SITE_ADDRESS |
http://localhost:8000 |
Public site address; dev server bind host/port |
ISCC_WEB_MEDIA_PATH |
media/ beside the package |
Storage directory for media packages |
ISCC_WEB_MAX_WORKERS |
CPU count | ISCC worker processes (memory-heavy per worker) |
ISCC_WEB_MAX_UPLOAD_SIZE |
1073741824 |
Max upload size in bytes |
ISCC_WEB_IO_READ_SIZE |
2097152 |
File read chunk size in bytes |
ISCC_WEB_PRIVATE_FILES |
true |
Restrict download/delete/embed to uploader |
ISCC_WEB_CORS_ORIGINS |
(empty) | CORS origins; empty disables CORS |
ISCC_WEB_STORAGE_EXPIRY |
3600 |
Seconds until uploads are deleted |
ISCC_WEB_CLEANUP_INTERVAL |
600 |
Cleanup interval in seconds; 0 deactivates |
ISCC_WEB_LOG_LEVEL |
DEBUG |
Loguru log level |
ISCC_WEB_SENTRY_DSN |
(empty) | Optional Sentry DSN |
Constraints and invariants¶
iscc_web/api/schema.pyis generated by datamodel-code-generator fromiscc_web/static/docs/openapi.yaml(config:[tool.datamodel-codegen]inpyproject.toml). Edit the yaml, thenuv run poe codegen. The file is omitted from coverage.- Controllers are registered as an import side effect: a controller module not star-imported in
iscc_web/__init__.pyis not routed (hence theF403ruff ignore for that file). openapi.yamland the implementation must stay in sync —tests/test_api_schemathesis.pyruns property-based tests against the spec.media/061knt35ejv6ois a permanent fixture:cleanup.pyskips it and tests assert its exact stored content. Never delete it.- Coverage is gated at 100% with branch coverage (
fail_underinpyproject.toml). The test server subprocess is measured too (concurrency = ["thread", "multiprocessing"]). - Tests run against a real uvicorn server subprocess (spawn context, port 44555 + xdist offset) —
no mocks. It shuts down gracefully via a multiprocessing
Event;terminate()discards coverage data and leaks pool worker processes. - Callables submitted to
Poolmust be picklable top-level functions (code_iscc,text_simprints,idk.extract_metadata,idk.embed_metadata). iscc_web/options.pymust be imported beforeiscc_sdk/iscc_sct/iscc_sci— it seeds their environment defaults at import time, and pool workers inherit them.- Style: ruff with line-length 120, LF line endings, target py313; prek hooks enforce whitespace/EOL fixes, mdformat for Markdown, and openapi-spec-validator for the spec.
- Renovate ignores
iscc-tika(renovate.jsonignoreDeps) — it must stay<0.5.0.
Change playbook¶
Add an endpoint¶
- Edit
iscc_web/static/docs/openapi.yaml(path + schemas), thenuv run poe formatopenapi. uv run poe codegen— regeneratesiscc_web/api/schema.py.- Implement an
APIControllersubclass iniscc_web/api/(class name becomes the route segment;version()returns"v1"). - Star-import the new module in
iscc_web/__init__.py— without this the routes do not exist. - Add integration tests in
tests/(they call the live server over httpx). uv run poe all— schemathesis validates spec conformance; coverage must stay at 100%.
Add a configuration option¶
- Add a field to
IsccWebOptionsiniscc_web/options.py(env name =ISCC_WEB_+ field name). - Cover it in
tests/test_options.py. Note:optsis instantiated at import time — test servers configure the environment before importingiscc_web(seetests/conftest.py). - Document it in
README.mdand Configuration.
Bump iscc-sdk / iscc-schema / iscc-core¶
- Expect every exact ISCC fixture assertion in
tests/to change: ISCC values,units,datahash/metahash, thegeneratorstring (iscc-sdk - vX.Y.Z), and the$schema/@contextURLs. - Update the stored fixture files under
media/061knt35ejv6o/and example values inopenapi.yamlif response content changes. - The Dockerfile pre-downloads the iscc-sct/iscc-sci ONNX models and runs
iscc-sdk install; CI caches the models — check both if model handling changes.
Change upload handling¶
FileHandler.handle_upload()iniscc_web/api/mixins.pyis shared byPOST /isccandPOST /media— both endpoints change together. Update both paths inopenapi.yaml.
Change the frontend¶
- The SPA talks to the backend only through
frontend/services/api.service.ts; tests live infrontend/tests/. The build writesiscc_web/static/dist/manifest.json, whichiscc_web/vite.pyresolves in production.
Common mistakes¶
NEVER hand-edit iscc_web/api/schema.py. It is generated and will be overwritten.
# WRONG: edit iscc_web/api/schema.py directly
# CORRECT: edit iscc_web/static/docs/openapi.yaml, then:
uv run poe codegen
NEVER send uploads as multipart/form-data. The API reads the raw request body and takes the
filename from the base64-encoded X-Upload-Filename header.
NEVER delete media/061knt35ejv6o. It is a permanent test fixture that cleanup.py
deliberately skips.
NEVER terminate() the test server subprocess. Signal its multiprocessing Event and
join() it — otherwise coverage data is discarded and pool workers leak.
NEVER add a controller without star-importing its module in iscc_web/__init__.py. It will
import fine, pass type checks, and silently never be routed.
NEVER pass lambdas, closures, or instance methods to the process Pool. Worker submissions
must pickle; use top-level module functions.
ALWAYS import iscc_web.options (or iscc_web) before iscc_sdk, iscc_sct, or iscc_sci
in new modules and scripts — the service's library defaults are applied to the environment when
options.py is imported.
ALWAYS run uv run poe all before committing. It reformats the spec, regenerates the schema,
formats code, runs the full test suite with the coverage gate, and checks complexity.