{"name":"nopaque-mcp","description":"TotalPath MCP. Call createMapping/getMapping to map IVR systems, runMissionTest/getMissionTestRun to execute scripted persona tests, runComplianceTest/getRunResults for regulatory probes, getWorkspaceUsage for capacity queries. Authenticate with an x-api-key header. No key yet? Call registerWorkspace (no credential needed) to create a workspace and receive one, then requestAnchorCode and verifyAnchorCode to have a human verify a mobile number, which unlocks free calling capacity. getClaimLink hands the workspace to that person to own and pay for. A person can also sign up and create a key at https://www.nopaque.co.uk/register. Key setup steps: https://www.nopaque.co.uk/docs/authentication.","version":"0.1.0","protocolVersion":"2025-11-25","transport":"streamable-http","endpoint":"https://mcp.nopaque.co.uk/","authentication":{"type":"apiKey","in":"header","name":"x-api-key"},"tools":[{"name":"createMapping","title":"Create IVR Mapping Job","description":"Create an IVR mapping job that will call the specified phone number, navigate the menu tree via DTMF and/or voice prompts, transcribe audio, and analyse with AI to discover all branches. Returns a job resource immediately with status=\"idle\". To start the actual mapping, call the startMapping REST endpoint (not yet exposed via MCP — use the REST API or wait for follow-up phases). Mapping completion typically takes 2-15 minutes depending on tree depth."},{"name":"getMapping","title":"Get Mapping Job","description":"Get the current state and run metadata for a specific IVR mapping job.\n\n    USE WHEN:\n    - User asks about a specific mapping job by ID\n    - User wants to know if a mapping run has finished\n    - User wants run-level metadata (stats, in-flight calls, start/completion times)\n    - You need a job's currentRun.id to feed into getMappingTree or REST /mapping/{id}/runs\n\n    QUERY PARAMS (optional):\n    - version: Run number (1-indexed) — fetches that specific historical run. Default: latest run.\n\n    JOB vs RUN STATUS (critical):\n    Mapping JOBS cycle status `running` ↔ `idle` and **never reach `completed`** between runs.\n    Only RUNS reach `completed` / `failed` / `limited`. To detect a finished run, read\n    `currentRun.status === 'completed'`. **Do NOT poll for `status === 'completed'` at the\n    job level — that state never arrives.** See getMappingTree for the discovered IVR tree,\n    available once `currentRun.status === 'completed'`.\n\n    RESPONSE FIELDS:\n    - id: Job UUID.\n    - status: Job-level status (always `idle` between runs; `running` during an active run).\n    - tags: String labels for grouping (set via createMapping or update).\n    - config: mappingMode, maxDepth, maxCalls, probeMode.\n    - currentRun: The run this response describes — the active/latest run by default, or the run\n      identified by `version` when supplied. Contains: id, status, runNumber, stats, inFlightCount,\n      limitReason, startedAt, completedAt. **Prefer `currentRun.status` over the flat `status`.**\n    - status / runNumber / stats / inFlightCount / limitReason / startedAt / completedAt: Flat-merged\n      duplicates of currentRun fields, kept for back-compat. New code should read currentRun.*.\n\n    USAGE PATTERNS:\n    - \"Is mapping <id> finished?\" → getMapping { id } then read `currentRun.status`\n    - \"Show me run 2 of mapping <id>\" → getMapping { id, version: 2 }\n    - \"What's the in-flight call count for mapping <id>?\" → getMapping { id } then read `currentRun.inFlightCount`\n\n    RELATED TOOLS:\n    - listMappings — find a job by phoneNumber/status/tag/createdAfter (use this before getMapping if you don't have the id)\n    - getMappingTree — discovered IVR tree once `currentRun.status === 'completed'` (use `?format=flat` for leaf-count / depth aggregation)\n    - cancelMapping — **DESTRUCTIVE** — stop an in-progress run; only call when explicitly asked\n    - createMapping — **REAL CALL** — starts a new mapping run (initiates external phone call); only call when explicitly asked\n    - REST GET /mapping/{id}/runs — run history (REST-only today; may surface as a future MCP tool if eval shows demand)\n\n    ERRORS:\n    - 404: job does not exist or belongs to another workspace (cross-workspace 404 by design — do not retry)\n    - 400: invalid version (non-integer or < 1)\n    - 404: requested version not found in this job's run history (response message lists available versions)"},{"name":"listMappings","title":"List Mapping Jobs","description":"List IVR mapping jobs in your workspace. Supports filtering and cursor pagination.\n\n    FILTERS (all optional):\n    - status: Filter by the latest run's status. Values: idle | running | completed | failed | limited.\n      IMPORTANT: Mapping JOBS cycle between status `running` (active mapping) and `idle` (between runs)\n      and NEVER reach `completed`. Only RUNS reach `completed`/`failed`/`limited`. The `status` filter\n      compares against the latest run's status — so `status=completed` returns jobs whose most recent\n      run finished, and `status=running` returns jobs with an in-flight run.\n    - tag: Filter to jobs with this tag (exact, lowercase alphanumeric + hyphens, e.g. 'compliance-eu').\n    - phoneNumber: Filter by exact E.164 phone number (e.g. '+441234567890').\n    - name: Case-insensitive substring match against the job name.\n    - profileId: Filter to jobs using a specific mapping profile (UUID).\n    - createdAfter / createdBefore: ISO-8601 datetime strings (e.g. '2026-05-01T00:00:00Z'). Efficient\n      DDB range filter — both, either, or neither.\n    - limit: Page size, 1-100, default 50.\n\n    SORTING:\n    - sortDir: 'asc' or 'desc' (default 'desc' = newest first). Sort is always by createdAt.\n\n    PAGINATION:\n    - Pass `cursor` (from a previous response's `nextCursor`) to get the next page.\n    - When `nextCursor` is absent from the response, you are on the last page.\n    - LOSSY-PAGINATION CAVEAT: When filtering by `status`, paged results may underfill (return fewer\n      than `limit` items) because the status filter is applied after the DDB query. Always follow\n      `nextCursor` until it is absent to be sure you have all matches.\n\n    RESULT FIELDS:\n    - id: UUID — use with `getMapping` for full detail (run state, step counts) or `getMappingTree`\n      for the discovered IVR tree (once the latest run has status=`completed`).\n    - status: The latest run's status; `idle` when the job has no active run.\n    - tags: String labels for grouping. Set via `createMapping` / mapping update.\n    - config: Includes mappingMode, maxDepth, maxCalls, probeMode.\n    - runNumber: The run number of the latest run, if any.\n\n    USAGE PATTERNS:\n    - \"List my 5 most recent mappings\" → { limit: 5, sortDir: 'desc' }\n    - \"Show running mapping jobs\" → { status: 'running' }\n    - \"Mappings tagged compliance-eu\" → { tag: 'compliance-eu' }\n    - \"Mappings created this week\" → { createdAfter: '<start-of-week ISO>' }\n    - \"Find runs for +44... in detail\" → call listMappings { phoneNumber: '+44...' } then getMapping(id)"},{"name":"cancelMapping","title":"Cancel Mapping Job","description":"Cancel an in-progress IVR mapping job. Returns the updated job resource. No effect if the job is already idle/cancelled. Use this when you need to stop a long-running mapping early (e.g., the agent realised the wrong number was supplied)."},{"name":"getMappingTree","title":"Get Discovered IVR Tree","description":"Get the discovered IVR tree structure for a mapping job — every node visited, the DTMF/voice paths between them, audio recordings, and (if enrichment ran) IVR menu transcripts and probe classifications.\n\n    USE WHEN:\n    - User asks for the discovered tree / map / structure of an IVR\n    - User wants leaf-node count, depth, or aggregate tree statistics (use `format=flat`)\n    - User wants to inspect specific tree-node fields (voice prompts, probe classifications, audio URLs)\n    - User wants to compare two historical runs (call twice with different `version`)\n\n    BEST CALLED: after `currentRun.status === 'completed'` (from getMapping). If you call earlier and\n    the run is still active, the response will document that via `reason: 'in_progress'` (see below).\n\n    QUERY PARAMS (optional):\n    - format: `tree` (default) = hierarchical nesting; `flat` = array of nodes with depth/path fields\n      for aggregation. Use `flat` for \"how many leaves?\" / \"how deep?\" / \"list all voice prompts\" questions.\n    - version: Run number (1-indexed) — fetches the tree of that historical run. Default: latest run.\n\n    RESPONSE — SUCCESS (tree built from steps):\n    - jobId, runId, runNumber, status, stats: identifiers + run-level summary.\n    - tree (when `format=tree`): a hierarchical TreeNode with .children[] recursion.\n    - steps (when `format=flat`): an array of TreeNode (each with empty .children, depth, path).\n\n    TreeNode fields (each is OPTIONAL — omitted when absent; never null per the AJV contract):\n    - stepId, digit, label, depth, path, status, transcript, isTerminal, children, duration\n    - stepType: 'dtmf' | 'voice' — interaction type (Phase 30 voice-agent)\n    - voicePrompt: spoken prompt text at this node (voice nodes only)\n    - menuLabel: semantic tag emitted per bot turn (e.g. 'greeting', 'capabilities_listed', 'balance_captured'); from Phase 55 voice-agent enrichment\n    - spokenResponse: what the agent said back at this turn (Phase 55 voice-agent)\n    - probeCategory / probeClassification / probeRationale: probe-enrichment fields (Phase 31). Only present\n      after probe enrichment ran. Trigger probes via REST POST /mapping/{id}/runs/{runId}/probe (not exposed\n      via MCP today).\n    - audioUrl: presigned S3 URL for the recorded audio of this step. **Valid for approximately 1 hour.\n      Do not cache or store** — re-call getMappingTree to refresh URLs for long sessions.\n    - inputRequired: per-step UX hint when a prompt requires user input. Contains type, description,\n      formatHint, terminator, startTimeMs.\n\n    RESPONSE — EMPTY-STATE ENVELOPE (200 OK, tree: null):\n    The handler returns 200 with `tree: null` and a `reason` discriminator in three cases:\n    - `reason: 'no_runs'` — the job has never been started. Message suggests POST /mapping/{id}/start.\n    - `reason: 'no_steps'` — a run started but no steps recorded yet. May still be initialising; retry shortly.\n    - `reason: 'in_progress'` — the latest run is still active and only the root step has been recorded.\n      Don't treat the current tree as complete. Retry shortly OR ask getMapping for `currentRun.status` to confirm.\n\n    All three empty-state cases return 200 OK with the same envelope shape; only `reason` + `message` differ.\n\n    USAGE PATTERNS:\n    - \"Show me the IVR tree for mapping <id>\" → getMappingTree { id }\n    - \"How many leaves does mapping <id> have?\" → getMappingTree { id, format: 'flat' } then filter steps where children.length === 0 OR isTerminal === true\n    - \"How deep is the tree?\" → getMappingTree { id, format: 'flat' } then max(steps[].depth)\n    - \"What voice prompts did mapping <id> discover?\" → getMappingTree { id, format: 'flat' } then filter where stepType === 'voice' or voicePrompt is present\n    - \"Show me run 1's tree\" → getMappingTree { id, version: 1 }\n    - \"What probe classifications fired?\" → getMappingTree { id } then walk tree filtering nodes where probeClassification is present\n\n    RELATED TOOLS:\n    - getMapping — job + run metadata, including `currentRun.status` (check this before deciding the tree is \"done\")\n    - listMappings — find jobs by phoneNumber/status/tag if you don't have the id\n    - REST GET /mapping/{id}/runs — run history (REST-only today; useful for picking a `version` for historical tree fetches)\n\n    ERRORS:\n    - 404: job does not exist or belongs to another workspace (cross-workspace 404 by design — do not retry)\n    - 400: invalid version (non-integer or < 1) OR invalid format (not 'tree' or 'flat')\n    - 404: requested version not found\n    - 200 + reason: 'no_runs' | 'no_steps' | 'in_progress' — NOT errors; empty-state envelope (see above)"},{"name":"runMissionTest","title":"Run Mission Test","description":"Launch a mission test run from an existing saved mission-test-config. A mission test calls a number, executes a scripted persona+goal+acceptance flow, and the LLM self-reports pass/fail against the acceptance criterion at end-of-call. Returns the run resource immediately; poll getMissionTestRun for verdict."},{"name":"getMissionTestRun","title":"Get Mission Test Run","description":"Use when you have a mission-test run id and want the full AI-judged result: verdict (pass/fail/inconclusive/error), passReasoning, passEvidence, transcript, and compliance fields. Returns 404 for non-mission runs (compliance, standard, param) — use getRunResults for those. For listing across runs use listTestRuns; for counts and percentages use aggregateTestRuns. Returns 404 cross-workspace (D-02 strict type-gate)."},{"name":"listMissionTestConfigs","title":"List Mission Test Configs","description":"List saved mission-test configs in your workspace with rich filters. Each saved config is a reusable mission-test definition (name, phone, sector, persona/mission, acceptance criterion, profile, optional tags). Filter by: `name` (substring), `phoneNumber` (E.164 exact), `sector` (e.g. `financial-services`), `profileId`, `tag` (lowercase exact, single tag), `createdAfter` / `createdBefore` (ISO8601). Sort by `createdAt` or `name`, asc/desc (default `createdAt desc`). Paginate via `cursor` + `limit` (1..100, default 50). Use when: the user wants to find or list configs by attribute (\"configs for compliance-EU\", \"configs for phone +44…\", \"configs tagged X\", \"configs created this month\"). Returns a slim projection — for full mission/acceptance text use getMissionTestConfig once you have identified the config. Mission configs are immutable today — there is no separate \"modified\" timestamp. To find configs newly added in a time window, filter by createdAfter / createdBefore. Cross-tool: for the run history launched from a specific config, use listTestRuns?configId=X. For pass-rate-by-config, use aggregateTestRuns?groupBy=configId. Mission configs are workspace-scoped; cross-workspace returns 404."},{"name":"getMissionTestConfig","title":"Get Mission Test Config","description":"Get a single saved mission-test config by ID. Returns the full row: name, description, phoneNumber, sector, mission, acceptance, profileId, tags, createdAt, updatedAt. Use when: you have a config ID (typically from listMissionTestConfigs or from a run.configId field) and want to inspect the persona / mission / acceptance criterion text — for example before launching a new run or to explain to the user how a previous run was configured. Cross-tool: for run history launched from this config, use listTestRuns?configId=X. For the run that produced a specific verdict, use getMissionTestRun. Configs are editable via PATCH /testing/mission-test-configs/{id}; `updatedAt` reflects the most recent edit and may differ from `createdAt`. Cross-workspace returns 404."},{"name":"getRunResults","title":"Get Run Results","description":"Get any test run (standard/param/mission/compliance) by id, including per-step results: outcome, transcript, similarity scores, words. Use when you need per-step breakdowns OR when you do not know if the run is mission or standard. For mission-specific shape (verdict, evidence, judgeReasoning) prefer getMissionTestRun. For listing across runs use listTestRuns; for counts use aggregateTestRuns. Returns 404 cross-workspace."},{"name":"listTestRuns","title":"List Test Runs","description":"List test runs (mission/compliance/standard/param) with rich filters: runType, outcome (PASS/FAIL/ERROR/INCONCLUSIVE), phoneNumber, configId, catalogueTestId, date range (startedAfter/startedBefore), sort (startedAt/completedAt asc/desc), and cursor pagination. Use cases: \"last 3 mission failures\", \"runs against phone +44… this week\", \"compliance fails in the last 24h\". For counts and percentages use aggregateTestRuns instead. D-11: Load tests live in a separate table — use listLoadTests; runType=load returns 400 here."},{"name":"aggregateTestRuns","title":"Aggregate Test Runs","description":"Aggregate test-run counts; groupBy outcome | runType | configId | catalogueTestId | phoneNumber; optional timeBucket day/week/month with UTC ISO8601 bucket keys (\"2026-05-12\" day, \"2026-W19\" week, \"2026-05\" month). Use cases: \"% pass/fail this week\", \"which compliance test fails most\", \"day-by-day pass count last 2 weeks\". Response capped at top 100 groups by count desc; truncated:boolean indicates cap hit. D-11: runType=load returns 400 — use listLoadTests. D-12: groupBy=stepName returns 400 — for step-level breakdowns within a single run use getRunResults; for step-level aggregation across runs see Phase 1.1."},{"name":"listLoadTests","title":"List Load Tests","description":"List all load-test runs in your workspace. Each load test fans out N concurrent test runs against a target number and produces aggregate statistics. Returns a paginated array."},{"name":"listComplianceCatalogue","title":"List Compliance Catalogue","description":"List the available compliance tests in the regulatory catalogue (e.g., GDPR consent capture, EU AI Act disclosure, PCI-DSS DTMF masking). Each entry has an ID + sector + description. Use the IDs as input to runComplianceTest."},{"name":"runComplianceTest","title":"Run Compliance Test","description":"Run a compliance test against a phone number. The test calls the number, executes a regulatory probe script (e.g., requests data deletion, asks about AI disclosure), and returns pass/fail per probe. Returns the run resource; poll getRunResults for completion."},{"name":"getWorkspaceUsage","title":"Get Workspace Usage","description":"Get the current usage and entitlement snapshot for your workspace: subscription tier, free-minute pool balances (testing/loadTesting/mapping/s2s/s2stest), purchased-pack balances, current-period consumption, and remaining LLM-enrichment tokens. Use this to check whether you have enough capacity before kicking off an expensive job. Returns 404 cross-workspace."},{"name":"registerWorkspace","title":"Register a Workspace and Get an API Key","description":"Create a new TotalPath workspace and receive an API key for it. No account, key or human is needed to call this. It is the first step for an agent that has no TotalPath credential yet.\n\n    USE WHEN:\n    - You have no TotalPath API key and want to start using this server\n    - You have been asked to set up TotalPath from scratch\n\n    DO NOT USE WHEN:\n    - You already hold a TotalPath API key. Calling this again creates a second, separate, empty workspace. It does not add anything to the one you have.\n\n    WHAT YOU GET BACK:\n    - workspaceId: the new workspace's id\n    - apiKey: your credential, beginning nq_. It is shown ONCE and cannot be retrieved again. Send it as the x-api-key header on every later call to this server (or Authorization: Bearer). Keep it out of transcripts and logs where you can.\n    - keyPrefix: the first characters of the key, safe to show a person\n    - scopes: what the key may do. It can create mappings and tests and read the balance. It can never buy capacity or change billing.\n\n    WHAT THE WORKSPACE CAN DO AT THIS POINT:\n    Create records only. It has NO calling capacity and cannot place a phone call until a human has verified a mobile number for it. Next, call requestAnchorCode.\n\n    RATE LIMITS:\n    A few registrations per hour from one network address. A refusal carries retryAfterSeconds. Do not retry in a loop.\n\n    RELATED TOOLS:\n    - requestAnchorCode, then verifyAnchorCode: unlock calling capacity by verifying a human's mobile\n    - getClaimLink: hand the workspace to a human so they can own it and pay for more capacity\n    - getWorkspaceUsage: read the balance"},{"name":"requestAnchorCode","title":"Send a Verification Code to a Human's Mobile","description":"Send a one-time code by SMS to a human's mobile phone, to verify that a real person stands behind this workspace. This is what unlocks calling capacity. The code goes to the PHONE, not to you. You then need to ask that person what the code says and pass it to verifyAnchorCode.\n\n    USE WHEN:\n    - You have just called registerWorkspace and the workspace has no capacity yet\n    - A person has agreed to be the accountable human for this workspace and has given you their mobile number\n\n    THE HUMAN'S PART:\n    Before calling this, tell the person that a text message is about to arrive and that you will need them to read the code back to you. Ask for the number in international format with the country code, for example +447700900123 or +12025550123.\n\n    ARGUMENTS:\n    - phoneNumber: the mobile number in E.164 format (a plus sign, country code, then digits, no spaces). Must be a MOBILE in the United Kingdom, Guernsey, Jersey, the Isle of Man or the United States. Landlines cannot receive the code.\n\n    RESPONSE:\n    - 202 with sent: true. The code is on its way. Now ask the person for it and call verifyAnchorCode with the same phoneNumber.\n\n    WHEN IT IS REFUSED:\n    - reason country_not_supported: only the countries above can be used\n    - reason not_a_mobile: the number is a landline or similar\n    - reason unparseable: check the format\n    - reason anchor_limit_reached (403): this workspace already holds the maximum number of verified humans. Waiting will not help. One of them has to be replaced first, which a person does from workspace settings. Note this is not returned when you re-request a code for a number this workspace already holds: that resend is always allowed, and it is what to do if a verifyAnchorCode retry told you the code had expired.\n    - reason already_anchored (409): that number is already the verified human for a different workspace. A number can stand behind one workspace at a time.\n    - 429 with retryAfterSeconds: too many codes this hour. Wait; do not resend in a loop. Each request sends a real text message to a real person.\n    - 403: this workspace has requested too many codes in its lifetime and will not be sent another.\n\n    RELATED TOOLS:\n    - verifyAnchorCode: submit the code the person read to you\n    - registerWorkspace: if you have no key yet"},{"name":"verifyAnchorCode","title":"Verify the Code and Unlock Calling Capacity","description":"Submit the code the human read to you from their text message. On success the number is permanently recorded as this workspace's accountable human, and the workspace receives its free calling capacity, so it can now run createMapping, runMissionTest and runComplianceTest.\n\n    USE WHEN:\n    - You called requestAnchorCode and the person has told you the code\n\n    ARGUMENTS:\n    - phoneNumber: the SAME number you passed to requestAnchorCode, in the same E.164 format\n    - code: the digits from the text message, exactly as the person read them\n\n    RESPONSE ON SUCCESS (200):\n    - anchored: true. The human is verified.\n    - freeGrantOutcome tells you about capacity:\n      - granted: free capacity was added. You can start work. Call getWorkspaceUsage to see how much.\n      - already-granted: this workspace already had it. Nothing is wrong.\n      - not-owed: this number has already used its one free allocation, on this or another workspace. The human is verified, but capacity has to be bought. Call getClaimLink so the person can pay.\n      - unavailable: the code was RIGHT and the human is verified, but the capacity grant could not be completed just now. Nothing is lost and nothing is double-spent. Wait a minute and call verifyAnchorCode again with the same arguments. If that retry is refused with reason bad_code, the code has expired, NOT been misread: do not ask the person to check it. Call requestAnchorCode for a fresh code and verify again; the grant is retried on that verification.\n      - refused: the grant was declined. Tell the person and call getClaimLink.\n\n    WHEN IT IS REFUSED:\n    - reason bad_code (400): the code did not match, or has expired. Codes expire after a few minutes. If the person only just read it, ask them to check it and try once more; otherwise call requestAnchorCode for a fresh one. If you are retrying after an unavailable outcome, treat bad_code as expired.\n    - reason already_anchored (409), or anchor_limit_reached (403): see requestAnchorCode\n    - reason not_held (409): the number you passed as replaces is not one of this workspace's verified humans. Do not guess at it.\n    - reason replaces_unparseable (400): the replaces number is not a number we can read. Ask the person to give it in full international format.\n    - reason survivor_is_target (400): replaces named the SAME number being verified, which would leave the workspace with nobody accountable.\n    - A 5xx means the verification provider had a problem, never that the code was wrong. Try again shortly.\n\n    RELATED TOOLS:\n    - getWorkspaceUsage: see the capacity you now have\n    - getClaimLink: let the human take ownership and buy more\n    - createMapping, runMissionTest, runComplianceTest: the work itself"},{"name":"getClaimLink","title":"Get a Link for a Human to Claim This Workspace","description":"Mint a one-time link that a human opens in a browser to take ownership of this workspace: they set an email and password, and can attach payment to buy more capacity. Give the link to the person. You never need to open it, and you cannot use it yourself.\n\n    USE WHEN:\n    - Free capacity is running low or exhausted (getWorkspaceUsage) and the person wants to keep going\n    - verifyAnchorCode returned freeGrantOutcome not-owed or refused\n    - The person wants to see the workspace, its call history or its keys in the TotalPath web app\n    - The person asks how to pay\n\n    WHAT YOU GET BACK (201):\n    - url: the link. Valid for ONE HOUR and usable ONCE. Pass it to the person as-is.\n    - expiresAt: when it stops working\n\n    AFTER THE PERSON CLAIMS:\n    Your API key keeps working exactly as before. The person now owns the workspace, can see everything you did, and can buy capacity that your key can then spend. Nothing you need to do.\n\n    WHEN IT IS REFUSED:\n    - 409 with reason workspace-claimed: a human already owns this workspace, so no more claim links can be issued. Tell the person to sign in at https://www.nopaque.co.uk instead.\n    - 503: the link could not be issued just now. Try again shortly.\n\n    A link that expires unused is harmless. Call this again for a fresh one.\n\n    RELATED TOOLS:\n    - getWorkspaceUsage: check the balance before deciding whether the person needs to pay\n    - verifyAnchorCode: must have succeeded before the workspace can do anything worth paying for"}],"documentation":"https://www.nopaque.co.uk/docs"}