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

# Python SDK

> QWED Python SDK documentation. Install via pip, configure sync or async clients, and verify LLM outputs with math, logic, code, and SQL engines.

The official Python SDK for QWED.

## Installation

```bash theme={null}
pip install qwed
```

## Quick start

```python theme={null}
from qwed_sdk import QWEDClient

client = QWEDClient(api_key="qwed_your_key")

# Basic verification
result = client.verify("Is 2+2=4?")
print(result.is_verified)  # True
print(result.status)       # "VERIFIED"
```

> [!NOTE]
>
> As of v5.0.0, the `status` field may return `INCONCLUSIVE`, `BLOCKED`, or `UNKNOWN` in addition to `VERIFIED` and `ERROR`. Natural-language math queries return `INCONCLUSIVE` when the inner engine succeeds, because the LLM translation step is non-deterministic. See the [trust boundary documentation](https://docs.qwedai.tech/api/endpoints.md#post-%2Fverify%2Fnatural_language) for details.

## How `is_verified` is decided

> [!NOTE]
>
> Changed in v8.0.0 ([GHSA-jppc-7r58-7pjh](https://github.com/QWED-AI/qwed-verification/security/advisories/GHSA-jppc-7r58-7pjh))

`VerificationResult.is_verified` follows the server's admission decision. The SDK applies the same rule as the server (`is_admitted()` in `qwed_sdk.models`):

* If the response carries an `admission` field, only `ADMIT` admits. `BLOCKED` is never verified.
* If the route returns no `admission`, `status` must be `VERIFIED` and `is_valid` must be `true`.

A bare `is_valid: true` or `is_verified: true` in the response never admits on its own. The server flattens engine fields into the response, so a result that a gate blocked can still carry the engine's `is_valid: true`. The server's decision is also available as `result.admission`.

```python theme={null}
result = client.verify_math("x**2 + 2*x + 1 = (x+1)**2")

if result.is_verified:
    run_downstream(result)
else:
    # BLOCKED, UNVERIFIABLE, or not admitted by the server
    reject(result.status, result.admission)
```

> [!WARNING]
>
> Before v8.0.0, the SDK set `is_verified` from a bare `is_valid` in the response. A result the server had marked `BLOCKED` or `UNVERIFIABLE` could read as verified. Upgrade to v8.0.0 or later.

## Async client

```python theme={null}
from qwed_sdk import QWEDAsyncClient
import asyncio

async def main():
    async with QWEDAsyncClient(api_key="qwed_...") as client:
        result = await client.verify("2+2=4")
        print(result.is_verified)

asyncio.run(main())
```

## Methods

### verify(query)

Auto-detect and verify any claim.

```python theme={null}
result = client.verify("What is 15% of 200?")
```

### verify\_math(expression)

Verify mathematical expressions.

```python theme={null}
result = client.verify_math("x**2 + 2*x + 1 = (x+1)**2")
```

### verify\_logic(query)

Verify logical constraints (QWED-Logic DSL).

```python theme={null}
result = client.verify_logic("(AND (GT x 5) (LT y 10))")
print(result.model)  # {"x": 6, "y": 9}
```

### verify\_code(code, language)

Check code for security vulnerabilities with the server's [`CodeVerifier`](https://docs.qwedai.tech/engines/code#security-scanning-codeverifier).

```python theme={null}
result = client.verify_code(code, language="python")
if not result.is_verified:
    print(result.status, result.admission)
    print(result.result.get("issues", []))
```

`verify_code` always sends the code to `POST /verify/code`, and `is_verified` follows the server's admission. If the client can't connect (`httpx.ConnectError`) or the request times out (`httpx.TimeoutException`), it returns `status="UNVERIFIABLE"` and `is_verified=False`. It never answers from the local [`CodeGuard`](https://docs.qwedai.tech/sdks/guards#codeguard) blocklist.

### verify\_sql(query, schema\_ddl, dialect)

Validate SQL queries against a schema with the server's [`SQLVerifier`](https://docs.qwedai.tech/engines/sql).

```python theme={null}
result = client.verify_sql(
    query="SELECT * FROM users WHERE id = 1",
    schema_ddl="CREATE TABLE users (id INT, name TEXT)",
    dialect="postgresql"
)
print(result.is_verified)  # True only when the server admits the query
```

`verify_sql` always sends the query to `POST /verify/sql`, and `is_verified` follows the server's admission. If the server can't be reached or the request times out, it returns `status="UNVERIFIABLE"` and `is_verified=False`. It never answers from the local [`SQLGuard`](https://docs.qwedai.tech/sdks/guards#sqlguard).

> [!WARNING]
>
> Before v8.0.0, `verify_code` and `verify_sql` answered from the local `CodeGuard` and `SQLGuard` when the full `qwed` package was installed, and reported a pass as `VERIFIED` without calling the server. Those blocklists missed reflective calls, stacked SQL statements, and dangerous SQL functions. If your code relied on offline `verify_code` or `verify_sql`, it now receives `UNVERIFIABLE` when the server is unreachable. See [GHSA-4rj3-8wqj-cqwc](https://github.com/QWED-AI/qwed-verification/security/advisories/GHSA-4rj3-8wqj-cqwc) and [GHSA-45jv-2j44-4c2q](https://github.com/QWED-AI/qwed-verification/security/advisories/GHSA-45jv-2j44-4c2q).

> [!NOTE]
>
> To check code without a server, use [`QWEDLocal.verify_code`](https://docs.qwedai.tech/advanced/qwed-local#3-code-security-codeverifier). It runs `CodeVerifier` in process and returns its admission in `evidence["admission"]`. Without the full `qwed` package, it returns `UNVERIFIABLE`.

### verify\_fact(claim, context)

> [!NOTE]
>
> New in v4.0.0

Verify factual claims against a provided context. Includes client-side PII pre-check.

```python theme={null}
result = client.verify_fact(
    claim="The company was founded in 2020",
    context="Acme Corp was established in 2020 in San Francisco."
)
print(result.status)       # "UNVERIFIABLE" (heuristic support is advisory-only)
print(result.is_verified)  # False
```

`verify_fact` fails closed when the QWED service can't complete the check. If the client can't connect (`httpx.ConnectError`) or the request times out (`httpx.TimeoutException`), it returns `status="UNVERIFIABLE"` and `is_verified=False`. It doesn't raise. The local PII pre-check still runs first, but it is not a fact check, so passing it never produces `VERIFIED`.

```python theme={null}
result = client.verify_fact(claim=claim, context=context)

if result.status == "UNVERIFIABLE":
    # Service unreachable, timed out, or the claim could not be proven.
    # Don't treat the claim as true. Retry, escalate, or reject.
    # Completed checks expose "agent_message"; offline/timeout failures expose
    # "message". Read both safely so neither path raises KeyError.
    print(result.result.get("agent_message") or result.result.get("message") or "Verification unavailable.")
    reject(claim)
elif result.status == "BLOCKED":
    reject(claim)
```

> [!WARNING]
>
> Earlier SDK versions returned `status="VERIFIED"` and `is_verified=True` when the service was unreachable, after running only the local PII scan. If your code relied on that offline pass, it now receives `UNVERIFIABLE`.

### verify\_stats(query, file\_path)

> [!NOTE]
>
> New in v4.0.0

Verify statistical claims against CSV data.

```python theme={null}
result = client.verify_stats(
    query="The average salary is above 50000",
    file_path="data.csv"
)
```

### verify\_consensus(query, mode, min\_confidence)

> [!NOTE]
>
> New in v4.0.0

Multi-engine consensus verification.

```python theme={null}
result = client.verify_consensus(
    query="The square root of 144 is 12",
    mode="high",
    min_confidence=0.8
)
print(result.is_verified)                 # True
print(result.result["meets_requirement"])  # True
```

`verify_consensus` takes `status` from the server's response. `is_verified` is `True` only when the server returns `meets_requirement` exactly `True`, which it sets only for a `VERIFIED` result that meets `min_confidence`. A truthy value that is not `True` admits nothing.

| Parameter | Type | Default | Description |
| - | - | - | - |
| `query` | str | — | Claim to verify |
| `mode` | str | `"single"` | `single`, `high`, or `maximum` |
| `min_confidence` | float | `0.8` | Minimum confidence threshold |

### verify\_image(image\_path, claim)

> [!NOTE]
>
> New in v4.0.0

Verify claims about image content.

```python theme={null}
result = client.verify_image(
    image_path="photo.jpg",
    claim="This image contains a cat"
)
```

### verify\_batch(items)

Verify multiple items at once. The response is a `BatchResult` whose `items` are per-item records — for math items the verdict is a [`DiagnosticResult`](https://docs.qwedai.tech/advanced/diagnostics) nested under each item's `result`, so check `result.status` and `result.proof_ref` per item rather than relying on an aggregate.

```python theme={null}
response = client.verify_batch([
    {"query": "2+2=4", "type": "math"},
    {"query": "x + x", "type": "math"},
])

for item in response.items:
    result = item.result or {}
    if result.get("status") == "VERIFIED" and result.get("proof_ref"):
        # Authoritative — admissible for control flow
        print("verified:", result.get("agent_message"), result.get("proof_ref"))
    else:
        # UNVERIFIABLE or BLOCKED — must reject
        print("rejected:", result.get("status"), result.get("agent_message"))
```

For `math` items, supply equality claims (e.g., `"x + x = 2*x"`) to receive proof results. Bare expressions without an `=` return `status: "UNVERIFIABLE"` with the simplified form in `developer_fields.simplified` — they are never reported as verified. The legacy `is_valid` flag is preserved inside `developer_fields.is_valid` for backward compatibility.

## Verification Context

> [!NOTE]
>
> New in v7.1.0

The SDK exposes [Verification Context](https://docs.qwedai.tech/specs/verification-context.md), the standardized JSON record of a verification, as client methods, re-exported types, and a `to_verification_context()` method on the verifier classes.

### Client methods

All three methods are available on both `QWEDClient` and `QWEDAsyncClient` and call the corresponding [Verification Context endpoints](https://docs.qwedai.tech/api/endpoints.md#verification-context-endpoints).

#### create\_verification\_context\_from\_diagnostic(diagnostic, query, verifier)

Create a schema-valid Verification Context document from a [`DiagnosticResult`](https://docs.qwedai.tech/advanced/diagnostics) dict. Optional keyword arguments: `verifier_version` and `attestation_token`. A `VERIFIED` diagnostic without a valid attestation token fails closed to `BLOCKED` (fail-closed). The admission is `DENY` unless the token also signs `ADMIT`, as the `attestation` of an answer the server admitted does. The server returns a v1.1 document, signed in its `attestation` field.

```python theme={null}
document = client.create_verification_context_from_diagnostic(
    diagnostic={
        "status": "VERIFIED",
        "agent_message": "Identity verified",
        "developer_fields": {"is_valid": True},
        "proof_ref": "sha256:9f2c…",
    },
    query="x**2 + 2*x + 1 = (x+1)**2",
    verifier="MathVerifier",
    attestation_token=token,
)
print(document["verdict"])                             # "VERIFIED"
print(document["context"]["decision"]["admission"])    # "ADMIT"
```

#### validate\_verification\_context(document, require\_attestation=True)

Validate a document against the JSON Schema of its `spec_version`. By default, the client first verifies the document's `attestation` against the server's keys (`/.well-known/jwks.json`), then asks the server, which checks it too.

```python theme={null}
result = client.validate_verification_context(document)
print(result["valid"])  # True or False
```

#### resolve\_verification\_context(document, require\_attestation=True)

Resolve the document's `proof_ref` evidence commitment, after the same attestation check. A `proof_ref` alone is a commitment anyone can compute; the attestation shows the server issued the document.

```python theme={null}
result = client.resolve_verification_context(document)
print(result["resolved"])  # True only if the attestation verifies and the stored proof_ref matches
```

When the attestation check fails, both methods return the failure without asking the server:

| Result | Cause |
| - | - |
| `{"valid": False, "error": "attestation_required"}` | The document carries no `attestation`, such as a v1.0 document |
| `{"valid": False, "error": "attestation_invalid"}` | The attestation does not verify, or does not sign this document |

`resolve_verification_context()` returns the same errors with `resolved` in place of `valid`. If the client can't fetch the server's keys, it raises `KeysUnavailableError` instead of reporting the document invalid. Pass `require_attestation=False` to check the schema or `proof_ref` alone, for example for a v1.0 document you built locally.

#### verify\_document\_attestation(document)

Verify a document's `attestation` against the server's keys and return its claims, or raise `AttestationError`. To verify offline, or against keys you pinned, use the module function with the JWKS you hold:

```python theme={null}
from qwed_sdk import AttestationError, verify_document_attestation

try:
    claims = verify_document_attestation(document, jwks)
except AttestationError as exc:
    print("not this server's document:", exc)
```

The signature authenticates the document. It adds nothing to the verdict or the admission: a signed `DENY` document is still `DENY`.

### Re-exported types and helpers

`qwed_sdk` re-exports the Verification Context v1.0 model, so you can build, validate, and resolve documents locally without an API round trip:

```python theme={null}
from qwed_sdk import (
    # Enums and model types
    Verdict,
    Admission,
    VerificationContext,
    VerificationContextDocument,
    VerificationContextValidationError,
    Formalization,
    VerifiedObject,
    Interpretation,
    Proof,
    Evidence,
    Decision,
    # proof_ref helpers
    compute_document_proof_ref,
    resolve_document_proof_ref,
    compute_context_proof_ref,
    resolve_context_proof_ref,
    # Schema validation helpers
    validate_document,
    is_valid_document,
)

# Validate a document dict (raises VerificationContextValidationError on failure)
validate_document(document)

# Boolean variant
if is_valid_document(document):
    ...

# Recompute and check the evidence commitment locally
assert resolve_document_proof_ref(document)
```

* `compute_document_proof_ref(document)` derives the canonical `sha256:<64-hex>` commitment for a document.
* `resolve_document_proof_ref(document)` returns `True` only when the document is `VERIFIED`, schema-valid, and the stored `proof_ref` matches the re-derived commitment. Everything else returns `False` (fail-closed).

### to\_verification\_context() on verifiers

Every verification engine that defines `to_verification_context()` maps its `DiagnosticResult` to a Verification Context document: the Code, Consensus, DSL Logic, Fact, Graph Fact, Image, Logic, Reasoning, Schema, SQL, Stats, and Symbolic verifier classes, plus the `BatchFactVerifier` and `MultiVLMVerifier` helpers. Math and the secure code executor do not define the method — pass their `DiagnosticResult` to [`create_verification_context_from_diagnostic()`](#create_verification_context_from_diagnostic-diagnostic-query-verifier) instead:

```python theme={null}
# Verifiers that define the method — convert the DiagnosticResult they return
diagnostic = sql_verifier.verify_sql(query="SELECT id, total FROM orders WHERE customer_id = 42")
document = sql_verifier.to_verification_context(
    diagnostic, query="SELECT id, total FROM orders WHERE customer_id = 42"
)
document.validate()
print(document.to_dict()["verdict"])

# Math and the secure code executor have no such method — pass their
# DiagnosticResult dict to the client instead
document = client.create_verification_context_from_diagnostic(
    diagnostic={
        "status": "VERIFIED",
        "agent_message": "Identity verified",
        "developer_fields": {"is_valid": True},
        "proof_ref": "sha256:9f2c…",
    },
    query="x**2 + 2*x + 1 = (x+1)**2",
    verifier="MathVerifier",
    attestation_token=token,
)
```

The mapping preserves the fail-closed invariants: `VERIFIED` documents carry a `sha256` `proof_ref`, while `UNVERIFIABLE` and `BLOCKED` documents carry `proof_ref: null` with admission `DENY`.

## Verification cache

`qwed_sdk.cache.VerificationCache` is the persistent SQLite-backed cache for verification results. It reduces LLM cost on repeated queries.

Cache entries are **context-bound**. A hit requires an exact match of both the normalized query and the trust-bound `CacheContext`.

A mismatch on any context dimension is a deterministic miss. The cache never falls back to a query-only match. This prevents cross-provider, cross-model, cross-policy, and cross-tenant replay of `VERIFIED` results.

### `CacheContext`

Every `get()` and `set()` call requires a `CacheContext`. All fields participate in the cache key.

| Field | Type | Required | Description |
| - | - | - | - |
| `provider` | `str` | yes | Provider or API endpoint identifier (e.g., `"openai"`). |
| `model` | `str` | yes | Model or deployment name (e.g., `"gpt-4o"`). |
| `policy_version` | `str` | yes | Verifier policy version string (e.g., `"v1"`). |
| `tenant_id` | `Optional[str]` | no | Tenant or session scope identifier. |
| `env_fingerprint` | `Optional[str]` | no | Environment or configuration fingerprint for extra binding. |

### Usage

```python theme={null}
from qwed_sdk.cache import CacheContext, VerificationCache

cache = VerificationCache()  # defaults to ~/.qwed/cache, TTL 24h

ctx = CacheContext(
    provider="openai",
    model="gpt-4o",
    policy_version="v1",
    tenant_id="tenant-alpha",
)

# Cache miss on first call
result = cache.get("Is 2+2=4?", ctx)         # None

# Populate the cache for this (query, context) pair
cache.set("Is 2+2=4?", {"verified": True, "value": 4}, ctx)

# Same query, same context → hit
cache.get("Is 2+2=4?", ctx)                  # {"verified": True, "value": 4}

# Same query, different provider → deterministic miss
ctx_other = CacheContext(provider="anthropic", model="claude-opus-4-5", policy_version="v1")
cache.get("Is 2+2=4?", ctx_other)            # None
```

### Miss conditions

`cache.get(query, context)` returns `None` when any of the following holds:

* No entry exists for this `(query, context)` pair.
* The stored entry has expired (TTL, default 24 hours).
* The stored context fingerprint does not match `context` (replay guard).
* The entry uses the legacy v1 schema. The cache never returns legacy rows.

### Constructor options

```python theme={null}
VerificationCache(cache_dir: Optional[str] = None, ttl: int = 86400)
```

* `cache_dir` — Directory for the SQLite database. Defaults to `~/.qwed/cache`.
* `ttl` — Time-to-live in seconds. Defaults to 24 hours.

> [!WARNING]
>
> `get()` and `set()` require a `CacheContext` argument as of the May 17, 2026 release. Calls that omit the context raise `TypeError`. See the [changelog entry](https://docs.qwedai.tech/changelog-archive#qwed-verification-—-context-bound-verification-cache-replay-prevention) for migration details.

## CLI

```bash theme={null}
# Verify
qwed verify "2+2=4"

# Verify logic
qwed verify-logic "(AND (GT x 5) (LT y 10))"

# Verify code file
qwed verify-code -f script.py

# Batch verify
qwed batch -f queries.json

# Verification Context utilities
qwed context validate --server http://localhost:8000 document.json
qwed context resolve --jwks-file jwks.json document.json
qwed context from-diagnostic --diagnostic-file diag.json \
  --query "x + x = 2*x" --verifier MathVerifier
```

See the [CLI reference](https://docs.qwedai.tech/advanced/cli.md#qwed-context-verification-context-utilities) for details on the `qwed context` command group.

## Verification cache

`qwed_sdk.cache` provides a context-bound SQLite cache for verification results.

A cache hit requires an exact match of both the normalized query **and** the trust-bound `CacheContext`. Any mismatch on a context dimension yields a deterministic miss — the cache never crosses trust boundaries. The cache also ignores legacy entries that lack a context fingerprint.

```python theme={null}
from qwed_sdk.cache import CacheContext, VerificationCache

ctx = CacheContext(
    provider="openai",
    model="gpt-4o",
    policy_version="v1",
    tenant_id="tenant-alpha",       # optional
    env_fingerprint="sha256-...",   # optional
)

cache = VerificationCache()

cache.get("2+2", ctx)              # None (miss)
cache.set("2+2", {"verified": True}, ctx)
cache.get("2+2", ctx)              # {"verified": True}

# Different provider — deterministic miss (replay prevention)
ctx2 = CacheContext(provider="claude", model="claude-opus-4-5", policy_version="v1")
cache.get("2+2", ctx2)             # None
```

### `CacheContext`

Trust-bound context dimensions required for every cache operation. All fields participate in the cache key; omitting or changing any field causes a deterministic cache miss.

**provider** `str` *required*

Provider or API endpoint identifier (for example, `"openai"`, `"claude"`).

**model** `str` *required*

Model or deployment name (for example, `"gpt-4o"`).

**policy\_version** `str` *required*

Verifier policy version string (for example, `"v1"`).

**tenant\_id** `Optional[str]` (default: None)

Tenant or session scope identifier. Use to prevent cross-tenant replay.

**env\_fingerprint** `Optional[str]` (default: None)

Environment or config fingerprint for additional binding.

### `VerificationCache(cache_dir=None, ttl=86400)`

**cache\_dir** `Optional[str]` (default: \~/.qwed/cache)

Directory for the SQLite cache database.

**ttl** `int` (default: 86400)

Time-to-live in seconds (default: 24 hours). `get()` treats entries older than `ttl` as a miss and deletes them on access.

The cache caps total entries at `MAX_ENTRIES = 1000`. When `set()` grows the table past the cap, `set()` evicts the least-recently-accessed entries.

#### `get(query, context)`

Return the cached result for `(query, context)`, or `None` on miss.

`get()` returns `None` when:

* No entry exists for the `(query, context)` pair.
* The entry has expired (older than `ttl`).
* The stored context fingerprint does not match `context` (defence-in-depth replay guard).

**query** `str` *required*

The verification query string. Normalized (lowercased, whitespace-collapsed) before hashing.

**context** `CacheContext` *required*

Trust-bound context that must match exactly. Omitting this argument raises `TypeError`.

**result** `Optional[Dict[str, Any]]`

The cached result dict, or `None` on any miss.

#### `set(query, result, context)`

Store a verification result bound to `(query, context)`.

The composite primary key `(key, context_fingerprint)` keeps identical queries under different contexts as independent entries.

**query** `str` *required*

The verification query string.

**result** `Dict[str, Any]` *required*

Verification result to cache. `set()` serializes this dict as JSON.

**context** `CacheContext` *required*

Trust-bound context to bind this entry to.

#### `clear()`

Remove all cached entries and reset stats.

#### `get_stats()`

Return a `CacheStats` dataclass with `hits`, `misses`, `total_entries`, `cache_size_bytes`, and a `hit_rate` property.

#### `print_stats()`

Print a formatted summary of cache statistics to stdout. Uses ANSI colors when `colorama` is installed.

> [!NOTE]
>
> **Breaking change (Issue #187).** `get()` and `set()` now require a `CacheContext` argument. Calls like `cache.get("2+2")` or `cache.set("2+2", result)` raise `TypeError`. The on-disk schema is `cache_v2` with composite `PRIMARY KEY (key, context_fingerprint)`. The cache never returns entries from the legacy v1 `cache` table.

## Guards

The SDK includes security guards for protecting AI agent pipelines:

```python theme={null}
from qwed_sdk.guards import (
    RAGGuard,
    ExfiltrationGuard,
    MCPPoisonGuard,
    SelfInitiatedCoTGuard,
    SovereigntyGuard,
    SystemGuard,
    ConfigGuard,
)

# Prevent RAG hallucinations from wrong document chunks
rag_guard = RAGGuard(max_drm_rate="1/10")
result = rag_guard.verify_retrieval_context(
    target_document_id="contract_v2",
    retrieved_chunks=chunks
)

# Block data exfiltration to unauthorized endpoints
exfil_guard = ExfiltrationGuard(allowed_endpoints=["https://api.openai.com"])
result = exfil_guard.verify_outbound_call(
    destination_url=url,
    payload=data
)

# Detect poisoned MCP tool definitions
mcp_guard = MCPPoisonGuard()
result = mcp_guard.verify_tool_definition(tool_schema)

# Block dangerous shell commands (v4.0.0)
sys_guard = SystemGuard()
result = sys_guard.verify_shell_command("rm -rf /")

# Detect plaintext secrets in config (v4.0.0)
cfg_guard = ConfigGuard()
result = cfg_guard.verify_config_safety(config_data)
```

See the [SDK Guards reference](https://docs.qwedai.tech/sdks/guards.md) for complete documentation.

## Environment variables

| Variable | Description |
| - | - |
| `QWED_API_KEY` | API key |
| `QWED_BASE_URL` | API base URL |


## Related topics

- [Installation](https://docs.qwedai.tech/getting-started/installation.md)
- [Agentic security guards for the QWED Python SDK](https://docs.qwedai.tech/sdks/guards.md)
- [QWED MCP: verification for Model Context Protocol](https://docs.qwedai.tech/mcp/overview.md)
- [Changelog Archive](https://docs.qwedai.tech/changelog-archive.md)
- [QWED GitHub Action](https://docs.qwedai.tech/advanced/github-action.md)


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