# did:web Document Auditor (`phoenix2810/did-web-document-auditor`) Actor

Audits a did:web (W3C Decentralized Identifier) document against the did:web method spec. Validates URL construction, id match, verification methods, relationship arrays, and service entries. Returns a score, grade, and recommendations.

- **URL**: https://apify.com/phoenix2810/did-web-document-auditor.md
- **Developed by:** [Sanskar Jaiswal](https://apify.com/phoenix2810) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## did:web Document Auditor

Audits a `did:web` decentralized identifier against the [W3C did:web method specification](https://w3c-ccg.github.io/did-method-web/). `did:web` anchors a Decentralized Identifier (DID) to a domain name and resolves it to a JSON DID document served over HTTPS. This actor constructs the DID document URL from the identifier, fetches it, and validates its structure against the spec: `@context`, `id`, `verificationMethod` entries, verification relationship arrays, and `service` entries.

### Use cases

- Organizations publishing a `did:web` identity verifying their DID document is spec-conformant before relying parties consume it.
- Decentralized identity and verifiable-credentials engineers debugging DID resolution failures.
- Protocol tooling and wallet developers checking real-world `did:web` server behavior against the spec.
- Agency consultants auditing a client's decentralized-identity deployment.
- CI/CD pipelines validating a `did.json` file after a deploy or key rotation.

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `identifiers` | array of strings | yes | `did:web` identifiers or plain domains/URLs to audit, e.g. `did:web:example.com`, `did:web:example.com:user:alice`, or `example.com`. Plain domains/URLs are converted into a `did:web` identifier automatically. Up to 50 per run. |
| `timeout` | integer | no | Request timeout in seconds, 3-60. Default `10`. |
| `maxRedirects` | integer | no | Maximum redirect hops to follow when fetching the DID document, 0-5. Default `3`. |
| `maxBodyBytes` | integer | no | Maximum response body size read, in bytes. Default `2097152` (2 MB). |

### Output

One JSON object per audited identifier, pushed to the default dataset.

| Field | Type | Description |
|---|---|---|
| `inputIdentifier` | string | The identifier or domain provided as input. |
| `resolvedDid` | string|null | The `did:web` identifier that was resolved (`null` if construction failed). |
| `resolvedUrl` | string|null | The constructed DID document URL that was fetched. |
| `finalUrl` | string|null | Final URL after following any redirects. |
| `https` | boolean|null | Whether the DID document was served over HTTPS. |
| `status` | integer|null | HTTP status code of the fetch. |
| `contentType` | string|null | Response `Content-Type` header. |
| `jsonValid` | boolean | Whether the response body parses as a valid JSON object. |
| `parseError` | string|null | JSON parse error message, if any. |
| `idMatch` | boolean | Whether the document's `id` field matches the resolved `did:web` identifier. |
| `context` | object|null | `@context` analysis: `present`, `hasCanonical`, `note`. |
| `verificationMethod` | object|null | `verificationMethod` analysis: `present`, `count`, per-entry `issues`. |
| `relationships` | object|null | Per-relationship analysis for `authentication`, `assertionMethod`, `keyAgreement`, `capabilityInvocation`, `capabilityDelegation`. |
| `serviceCount` | integer | Number of entries in the `service` array. |
| `serviceIssues` | array | Malformed-service-entry issue descriptions. |
| `services` | array | Service entries reported as data only; `serviceEndpoint` values are never fetched. |
| `score` | integer | Conformance score, 0-100. |
| `grade` | string | Letter grade, `A` to `F`. |
| `checks` | array | Per-check breakdown: `name`, `status`, `note`, `weight`, `recommendation`. |
| `issues` | array | Aggregated issue descriptions. |
| `recommendations` | array | Actionable fixes. |
| `checkedAt` | string | ISO timestamp of the audit. |
| `error` | string|null | Top-level construction or fetch error, if any. |

### Example input

```json
{
  "identifiers": ["did:web:well-known-showcase.paulkinlan-ea.deno.net"],
  "timeout": 10
}
```

### Example output

```json
{
  "inputIdentifier": "did:web:well-known-showcase.paulkinlan-ea.deno.net",
  "resolvedDid": "did:web:well-known-showcase.paulkinlan-ea.deno.net",
  "resolvedUrl": "https://well-known-showcase.paulkinlan-ea.deno.net/.well-known/did.json",
  "https": true,
  "status": 200,
  "contentType": "application/json; charset=utf-8",
  "jsonValid": true,
  "idMatch": true,
  "context": { "present": true, "hasCanonical": true, "note": "'@context' includes the canonical DID context." },
  "verificationMethod": { "present": true, "count": 1, "allValid": true },
  "serviceCount": 1,
  "serviceIssues": [],
  "score": 100,
  "grade": "A",
  "issues": [],
  "recommendations": ["did:web document looks spec-conformant. Re-run this audit after document updates to catch regressions."]
}
```

### How URL construction works

Per the did:web method spec's resolution algorithm:

1. Strip the `did:web:` prefix (or treat a plain domain/URL input as the method-specific identifier).
2. Replace `:` with `/` to obtain the domain and optional path, except a percent-encoded port colon (`%3A`), which is percent-decoded back to a literal `:` on the host instead of becoming a path separator.
3. Prepend `https://`.
4. No path segments: append `/.well-known/did.json`.
5. Path segments present: append `/<segment>/<segment>.../did.json`.

Before any HTTP request is made, every path segment is rejected if it is empty, `.`, `..`, or percent-decodes to contain `/` or `\`. This defends against path-traversal and DID-confusion attacks where a crafted identifier could be resolved to an unintended document.

### Security

- Only public HTTP/HTTPS targets are fetched. URLs containing userinfo/credentials (`user:pass@host`) are rejected.
- Localhost and private/link-local IPv4 and IPv6 literal ranges (RFC 1918, RFC 3927, and IPv6 unique-local/link-local equivalents, including bracketed IPv6 literals) are blocked.
- Hostnames are DNS-resolved and the resolved addresses are re-checked against the same private-range rules before any request is made (defense against DNS rebinding).
- Redirects are followed manually (up to `maxRedirects` hops) and each hop is revalidated with the same SSRF checks before the next request.
- Response bodies are capped at `maxBodyBytes` (default 2 MB).
- The actor fetches exactly one URL: the constructed DID document URL (and its validated redirect targets). `serviceEndpoint` values and any other URLs or DID references discovered inside the fetched document are reported as data only and are never fetched or resolved by this actor.
- No login, no credential collection, no private-data extraction.

### Pricing

| Event | Price (USD) |
|---|---|
| Actor start | $0.005 |
| Identifier audited | $0.01 |

Apify takes a 20% platform commission; the operator keeps 80%.

### FAQ

**Can I pass a plain domain instead of a full `did:web` identifier?** Yes. Supplying `example.com` is equivalent to `did:web:example.com`. Supplying `example.com/user/alice` is equivalent to `did:web:example.com:user:alice`.

**What happens if the identifier includes a non-standard port?** Use the did:web percent-encoded form, e.g. `did:web:example.com%3A8443:user:alice`, which resolves to `https://example.com:8443/user/alice/did.json`.

**Does the actor fetch the `serviceEndpoint` URLs listed in a DID document?** No. Service entries are reported as data only, the same way other `.well-known` auditors in this portfolio report discovered links without ever fetching them. This keeps the actor from being used to pivot into auditing arbitrary third-party URLs referenced by someone else's DID document.

**Why is a mismatched `id` field treated as a hard failure?** The did:web resolution algorithm requires verifying that the resolved document's `id` matches the DID being resolved (step 7). A mismatch means the document cannot be trusted to represent the queried identifier, so it is weighted heavily in the score.

**Are `publicKeyBase58` and `publicKeyPem` still valid?** They are legacy/deprecated key-material representations. `publicKeyPem` is flagged explicitly as deprecated; prefer `publicKeyJwk` or `publicKeyMultibase` for new verification methods.

# Actor input Schema

## `identifiers` (type: `array`):

did:web identifiers to audit, e.g. 'did:web:example.com', 'did:web:example.com:user:alice', or a plain domain/URL such as 'example.com' (the actor will construct the did:web identifier). Up to 50 per run.

## `timeout` (type: `integer`):

Timeout for the DID document HTTP request.

## `maxRedirects` (type: `integer`):

Maximum number of HTTP redirect hops to follow when fetching the DID document. Each hop is re-validated against the same SSRF rules.

## `maxBodyBytes` (type: `integer`):

Maximum number of bytes read from the DID document response body.

## Actor input object example

```json
{
  "identifiers": [
    "did:web:did-method-web.w3c-ccg.io"
  ],
  "timeout": 10,
  "maxRedirects": 3,
  "maxBodyBytes": 2097152
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "identifiers": [
        "did:web:did-method-web.w3c-ccg.io"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("phoenix2810/did-web-document-auditor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "identifiers": ["did:web:did-method-web.w3c-ccg.io"] }

# Run the Actor and wait for it to finish
run = client.actor("phoenix2810/did-web-document-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "identifiers": [
    "did:web:did-method-web.w3c-ccg.io"
  ]
}' |
apify call phoenix2810/did-web-document-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,phoenix2810/did-web-document-auditor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3dKBGwwff0zTgJVps/builds/Fuh6nV6qlsIMh89L9/openapi.json
