ToolDescriptor tools.tool@1

Generated at from openvibe-contracts v0.33.0 and openvibe-sdk v0.5.0.

Version
1.0.0
Owner
tools
Visibility
public
Status
active
Compatibility
backward
Decision
ADR-027
Schema
https://openvibe.network/contracts/tools/tool.v1.json

One tool on openvibe.tools as its registry describes it (GET /api/v1/tools/:id, and each item of GET /api/v1/tools; capability tools.tool.read; ADR-027). There is one descriptor per catalogue tool (GET /api/catalog.json tools[].id), built from the tool's own code, so its page, the run API, openvibe-sdk/tools, the OpenAPI document and the docs all read the same facts. EXECUTION is where the tool's engine runs for its page: client (in the browser; nothing leaves it), sync (a server request answered inline) or job (an asynchronous job on a satellite, tools.job@1). API says whether POST /api/v1/tools/{id}/run exposes the tool. A client tool has api true only when it also has a server engine (a pure transform that runs in Node), and its page stays browser-only. A page-only tool (yt) has api false and run null. A job tool's run creates a job of run.job.type whose input is { ...input, ...run.job.preset, tool: run.job.operation } (the preset and the operation always win). INPUT is the JSON Schema (2020-12) that a run request's input must match, embedded or as { $ref: https://openvibe.tools/api/v1/tools/{id}/schema#/$defs/input }. GET /api/v1/tools/:id/schema answers { $schema, $id, $defs: { input, output } }, and the list uses the $ref form. RULES this schema enforces: api false has no run, and api true has a run and an input; an API job tool names its job, and only job tools name one; an API tool whose output is a file runs as a job (its results are served as job files); a tools.net.probe tool fetches (egress) and is never anonymous; an anonymous egress tool has a per-target throttle (limits.perTargetPerMinute); a client tool never fetches; an unavailable tool says why (statusReason); JSON output has a schema. contracts.tools.checkDescriptor(d) also checks what depends on the id (run.path, $ref targets) and files.min <= files.max.

Fields

FieldTypeRequiredDescriptionConstraints
idstringyesThe tool's catalogue id, which is its subdomain label (png, jsminify, dns, pdf2jpg). It never changes.
  • pattern ^[a-z][a-z0-9-]{0,39}$
familystringyesThe catalogue family: net, dev, img, audio, docs, text, media, places, pastes…
  • pattern ^[a-z][a-z0-9-]{1,39}$
namestringyes
  • minLength 1
  • maxLength 80
summarystringyesOne plain sentence: what the tool does.
  • minLength 1
  • maxLength 200
statusenumyesstable: works, and its input and output only grow. beta: works; input or output may still change in a minor. preview: works with known gaps (limits or output not final). unavailable: listed, but runs are refused with 503 tools.tool.unavailable and the page says so (statusReason).
  • one of "stable", "beta", "preview", "unavailable"
statusReasonstringWhy the status is what it is, for people (e.g. 'needs qpdf on the host'). Required when unavailable.
  • minLength 1
  • maxLength 300
executionenumyesWhere the engine runs for the tool's page: client (the browser), sync (a server request answered inline), job (an asynchronous job).
  • one of "client", "sync", "job"
apibooleanyesPOST /api/v1/tools/{id}/run exposes the tool. false for a client tool without a server engine and for page-only tools (yt).
runobject | nullyesHow to call it; null when api is false.
inputone ofyesJSON Schema (2020-12) of the run request's input: embedded (an object schema), or { $ref } to $defs/input of GET /api/v1/tools/:id/schema. null only for a tool without an API.
filesobject | nullyesThe files a run takes (multipart parts, or tools.run-request@1 files references); null when it takes none.
outputobjectyes
  • no other fields
output.kindenumyesjson: result.data; text: result.text; file or files: result.files (job result files).
  • one of "json", "file", "files", "text"
output.schemaone ofJSON Schema of result.data (kind json): embedded, or { $ref } to $defs/output of GET /api/v1/tools/:id/schema.
output.mimearray of stringMedia types the result files can have (kind file or files).
  • minItems 1
  • unique items
  • items: pattern ^[a-z]+/([a-z0-9][a-z0-9.+-]*|\*)$
limitsobjectyes
  • no other fields
limits.timeoutMsintegeryesA run (or its job) longer than this fails with tools.job.timeout or 504.
  • minimum 1
  • maximum 3600000
limits.maxDurationSecintegerLongest audio or video input.
  • minimum 1
limits.maxPixelsintegerLargest image input (width × height).
  • minimum 1
limits.maxPagesintegerMost PDF pages.
  • minimum 1
limits.maxInputBytesintegerLargest input as JSON (text to transform, lists of hosts…).
  • minimum 1
limits.perTargetPerMinuteintegerEgress tools: most runs per minute against one target host or address, across all callers.
  • minimum 1
authobjectyesCaller tiers: anonymous < session < user < app/service.
  • no other fields
auth.anonymousbooleanyestrue: a caller with no token and no browser session may run it (keyed by IP, IPv6 by /64). false: a browser session (the ov_tools_jobs cookie), a signed-in person or a token is needed.
auth.capabilityenumyesWhat an app or service token must hold. tools.tool.run is public. tools.net.probe (partner) covers network probes: only principals holding it run these through the API; people use their pages, which have a per-target throttle.
  • one of "tools.tool.run", "tools.net.probe"
quotaClassstringyesThe quota bucket a run counts against, with allowances per caller tier. Names describe the work, never a tier or a price (tools-run, tools-job, tools-probe, tools-fetch…), so a paid tier later adds allowances, not names.
  • pattern ^tools-[a-z0-9]+(-[a-z0-9]+)*$
costintegeryesRelative weight of one run within its quota class (1 = a cheap lookup or transform). Quotas count weight, not requests.
  • minimum 1
  • maximum 1000
egressbooleanyesThe server fetches a host or URL the caller chose (DNS, WHOIS, headers, Open Graph, ports…), always through the SSRF guard.
hostsarray of stringyesThe tool's public hostnames, canonical first (png.openvibe.tools, a custom domain…); empty for a tool that lives on another service.
  • maxItems 50
  • unique items
  • items: pattern ^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$
docsstringyesWhere a person reads about the tool.
  • pattern ^https://
  • format uri

Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

Valid: dns-sync-egress
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Valid: jsonminify-client-engine
{
  "id": "jsonminify",
  "family": "dev",
  "name": "JSON Minifier",
  "summary": "Strip the whitespace out of JSON.",
  "status": "stable",
  "execution": "client",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/jsonminify/run",
    "job": null
  },
  "input": {
    "type": "object",
    "required": [
      "text"
    ],
    "additionalProperties": false,
    "properties": {
      "text": {
        "type": "string",
        "maxLength": 1048576
      }
    }
  },
  "files": null,
  "output": {
    "kind": "text"
  },
  "limits": {
    "timeoutMs": 5000,
    "maxInputBytes": 1048576
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-run",
  "cost": 1,
  "egress": false,
  "hosts": [
    "json-minifier.openvibe.tools",
    "jsonminify.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/jsonminify"
}
Valid: logo-client-only
{
  "id": "logo",
  "family": "text",
  "name": "Logo Maker",
  "summary": "Make a text logo in the browser.",
  "status": "stable",
  "execution": "client",
  "api": false,
  "run": null,
  "input": null,
  "files": null,
  "output": {
    "kind": "file",
    "mime": [
      "image/png",
      "image/svg+xml"
    ]
  },
  "limits": {
    "timeoutMs": 1000
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-run",
  "cost": 1,
  "egress": false,
  "hosts": [
    "logo.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/logo"
}
Valid: png-job
{
  "id": "png",
  "family": "img",
  "name": "PNG Converter",
  "summary": "Convert JPG, WebP, AVIF, HEIC, GIF, BMP, TIFF and more to PNG.",
  "status": "stable",
  "execution": "job",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/png/run",
    "job": {
      "type": "img.process",
      "operation": "convert",
      "preset": {
        "format": "png"
      }
    },
    "legacy": [
      "POST /api/process",
      "POST /api/process/direct"
    ]
  },
  "input": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "compressionLevel": {
        "type": "integer",
        "minimum": 0,
        "maximum": 9
      },
      "width": {
        "type": "integer",
        "minimum": 1,
        "maximum": 16384
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "image/*"
    ],
    "maxBytes": 52428800
  },
  "output": {
    "kind": "file",
    "mime": [
      "image/png"
    ]
  },
  "limits": {
    "timeoutMs": 120000,
    "maxPixels": 100000000
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 5,
  "egress": false,
  "hosts": [
    "png.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/png"
}
Valid: port-probe
{
  "id": "port",
  "family": "net",
  "name": "Port Checker",
  "summary": "Check whether TCP ports on a host are open.",
  "status": "beta",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/port/run",
    "job": null,
    "legacy": [
      "GET /api/net/port/:target"
    ]
  },
  "input": {
    "type": "object",
    "required": [
      "host",
      "ports"
    ],
    "additionalProperties": false,
    "properties": {
      "host": {
        "type": "string",
        "maxLength": 253
      },
      "ports": {
        "type": "array",
        "minItems": 1,
        "maxItems": 20,
        "items": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        }
      }
    }
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "type": "object",
      "properties": {
        "results": {
          "type": "array"
        }
      }
    }
  },
  "limits": {
    "timeoutMs": 15000,
    "perTargetPerMinute": 6
  },
  "auth": {
    "anonymous": false,
    "capability": "tools.net.probe"
  },
  "quotaClass": "tools-probe",
  "cost": 5,
  "egress": true,
  "hosts": [
    "port.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/port"
}
Valid: protectpdf-unavailable
{
  "id": "protectpdf",
  "family": "docs",
  "name": "Protect PDF",
  "summary": "Put a password on a PDF (AES-256).",
  "status": "unavailable",
  "statusReason": "Needs qpdf on the host; not deployed yet.",
  "execution": "job",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/protectpdf/run",
    "job": {
      "type": "docs.process",
      "operation": "protect"
    }
  },
  "input": {
    "type": "object",
    "required": [
      "password"
    ],
    "properties": {
      "password": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "application/pdf"
    ],
    "maxBytes": 104857600
  },
  "output": {
    "kind": "file",
    "mime": [
      "application/pdf"
    ]
  },
  "limits": {
    "timeoutMs": 300000,
    "maxPages": 2000
  },
  "auth": {
    "anonymous": false,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 10,
  "egress": false,
  "hosts": [
    "protectpdf.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/protectpdf"
}
Valid: yt-page-only
{
  "id": "yt",
  "family": "media",
  "name": "YouTube Downloader",
  "summary": "Save a YouTube video or its audio.",
  "status": "stable",
  "execution": "job",
  "api": false,
  "run": null,
  "input": null,
  "files": null,
  "output": {
    "kind": "file",
    "mime": [
      "video/mp4",
      "audio/mpeg"
    ]
  },
  "limits": {
    "timeoutMs": 900000,
    "maxDurationSec": 3600,
    "perTargetPerMinute": 20
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-download",
  "cost": 50,
  "egress": true,
  "hosts": [
    "youtube-downloader.openvibe.tools",
    "yt.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/yt"
}
Rejected: anonymous-egress-without-throttle
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: api-false-with-run
{
  "id": "png",
  "family": "img",
  "name": "PNG Converter",
  "summary": "Convert JPG, WebP, AVIF, HEIC, GIF, BMP, TIFF and more to PNG.",
  "status": "stable",
  "execution": "job",
  "api": false,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/png/run",
    "job": {
      "type": "img.process",
      "operation": "convert",
      "preset": {
        "format": "png"
      }
    },
    "legacy": [
      "POST /api/process",
      "POST /api/process/direct"
    ]
  },
  "input": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "compressionLevel": {
        "type": "integer",
        "minimum": 0,
        "maximum": 9
      },
      "width": {
        "type": "integer",
        "minimum": 1,
        "maximum": 16384
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "image/*"
    ],
    "maxBytes": 52428800
  },
  "output": {
    "kind": "file",
    "mime": [
      "image/png"
    ]
  },
  "limits": {
    "timeoutMs": 120000,
    "maxPixels": 100000000
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 5,
  "egress": false,
  "hosts": [
    "png.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/png"
}
Rejected: api-job-tool-without-job
{
  "id": "png",
  "family": "img",
  "name": "PNG Converter",
  "summary": "Convert JPG, WebP, AVIF, HEIC, GIF, BMP, TIFF and more to PNG.",
  "status": "stable",
  "execution": "job",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/png/run",
    "job": null,
    "legacy": [
      "POST /api/process",
      "POST /api/process/direct"
    ]
  },
  "input": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "compressionLevel": {
        "type": "integer",
        "minimum": 0,
        "maximum": 9
      },
      "width": {
        "type": "integer",
        "minimum": 1,
        "maximum": 16384
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "image/*"
    ],
    "maxBytes": 52428800
  },
  "output": {
    "kind": "file",
    "mime": [
      "image/png"
    ]
  },
  "limits": {
    "timeoutMs": 120000,
    "maxPixels": 100000000
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 5,
  "egress": false,
  "hosts": [
    "png.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/png"
}
Rejected: api-true-without-input
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": null,
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: api-true-without-run
{
  "id": "png",
  "family": "img",
  "name": "PNG Converter",
  "summary": "Convert JPG, WebP, AVIF, HEIC, GIF, BMP, TIFF and more to PNG.",
  "status": "stable",
  "execution": "job",
  "api": true,
  "run": null,
  "input": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "compressionLevel": {
        "type": "integer",
        "minimum": 0,
        "maximum": 9
      },
      "width": {
        "type": "integer",
        "minimum": 1,
        "maximum": 16384
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "image/*"
    ],
    "maxBytes": 52428800
  },
  "output": {
    "kind": "file",
    "mime": [
      "image/png"
    ]
  },
  "limits": {
    "timeoutMs": 120000,
    "maxPixels": 100000000
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 5,
  "egress": false,
  "hosts": [
    "png.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/png"
}
Rejected: client-tool-fetches
{
  "id": "jsonminify",
  "family": "dev",
  "name": "JSON Minifier",
  "summary": "Strip the whitespace out of JSON.",
  "status": "stable",
  "execution": "client",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/jsonminify/run",
    "job": null
  },
  "input": {
    "type": "object",
    "required": [
      "text"
    ],
    "additionalProperties": false,
    "properties": {
      "text": {
        "type": "string",
        "maxLength": 1048576
      }
    }
  },
  "files": null,
  "output": {
    "kind": "text"
  },
  "limits": {
    "timeoutMs": 5000,
    "maxInputBytes": 1048576,
    "perTargetPerMinute": 10
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-run",
  "cost": 1,
  "egress": true,
  "hosts": [
    "json-minifier.openvibe.tools",
    "jsonminify.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/jsonminify"
}
Rejected: file-output-answered-inline
{
  "id": "png",
  "family": "img",
  "name": "PNG Converter",
  "summary": "Convert JPG, WebP, AVIF, HEIC, GIF, BMP, TIFF and more to PNG.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/png/run",
    "job": null,
    "legacy": [
      "POST /api/process",
      "POST /api/process/direct"
    ]
  },
  "input": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "compressionLevel": {
        "type": "integer",
        "minimum": 0,
        "maximum": 9
      },
      "width": {
        "type": "integer",
        "minimum": 1,
        "maximum": 16384
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "image/*"
    ],
    "maxBytes": 52428800
  },
  "output": {
    "kind": "file",
    "mime": [
      "image/png"
    ]
  },
  "limits": {
    "timeoutMs": 120000,
    "maxPixels": 100000000
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 5,
  "egress": false,
  "hosts": [
    "png.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/png"
}
Rejected: input-schema-not-an-object
{
  "id": "jsonminify",
  "family": "dev",
  "name": "JSON Minifier",
  "summary": "Strip the whitespace out of JSON.",
  "status": "stable",
  "execution": "client",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/jsonminify/run",
    "job": null
  },
  "input": {
    "type": "string"
  },
  "files": null,
  "output": {
    "kind": "text"
  },
  "limits": {
    "timeoutMs": 5000,
    "maxInputBytes": 1048576
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-run",
  "cost": 1,
  "egress": false,
  "hosts": [
    "json-minifier.openvibe.tools",
    "jsonminify.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/jsonminify"
}
Rejected: json-output-without-schema
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json"
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: mime-with-parameters
{
  "id": "png",
  "family": "img",
  "name": "PNG Converter",
  "summary": "Convert JPG, WebP, AVIF, HEIC, GIF, BMP, TIFF and more to PNG.",
  "status": "stable",
  "execution": "job",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/png/run",
    "job": {
      "type": "img.process",
      "operation": "convert",
      "preset": {
        "format": "png"
      }
    },
    "legacy": [
      "POST /api/process",
      "POST /api/process/direct"
    ]
  },
  "input": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "compressionLevel": {
        "type": "integer",
        "minimum": 0,
        "maximum": 9
      },
      "width": {
        "type": "integer",
        "minimum": 1,
        "maximum": 16384
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "image/png; charset=binary"
    ],
    "maxBytes": 52428800
  },
  "output": {
    "kind": "file",
    "mime": [
      "image/png"
    ]
  },
  "limits": {
    "timeoutMs": 120000,
    "maxPixels": 100000000
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 5,
  "egress": false,
  "hosts": [
    "png.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/png"
}
Rejected: probe-anonymous
{
  "id": "port",
  "family": "net",
  "name": "Port Checker",
  "summary": "Check whether TCP ports on a host are open.",
  "status": "beta",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/port/run",
    "job": null,
    "legacy": [
      "GET /api/net/port/:target"
    ]
  },
  "input": {
    "type": "object",
    "required": [
      "host",
      "ports"
    ],
    "additionalProperties": false,
    "properties": {
      "host": {
        "type": "string",
        "maxLength": 253
      },
      "ports": {
        "type": "array",
        "minItems": 1,
        "maxItems": 20,
        "items": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        }
      }
    }
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "type": "object",
      "properties": {
        "results": {
          "type": "array"
        }
      }
    }
  },
  "limits": {
    "timeoutMs": 15000,
    "perTargetPerMinute": 6
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.net.probe"
  },
  "quotaClass": "tools-probe",
  "cost": 5,
  "egress": true,
  "hosts": [
    "port.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/port"
}
Rejected: probe-without-egress
{
  "id": "port",
  "family": "net",
  "name": "Port Checker",
  "summary": "Check whether TCP ports on a host are open.",
  "status": "beta",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/port/run",
    "job": null,
    "legacy": [
      "GET /api/net/port/:target"
    ]
  },
  "input": {
    "type": "object",
    "required": [
      "host",
      "ports"
    ],
    "additionalProperties": false,
    "properties": {
      "host": {
        "type": "string",
        "maxLength": 253
      },
      "ports": {
        "type": "array",
        "minItems": 1,
        "maxItems": 20,
        "items": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        }
      }
    }
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "type": "object",
      "properties": {
        "results": {
          "type": "array"
        }
      }
    }
  },
  "limits": {
    "timeoutMs": 15000,
    "perTargetPerMinute": 6
  },
  "auth": {
    "anonymous": false,
    "capability": "tools.net.probe"
  },
  "quotaClass": "tools-probe",
  "cost": 5,
  "egress": false,
  "hosts": [
    "port.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/port"
}
Rejected: quota-class-without-prefix
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: ref-not-a-string
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": 42
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: run-get-method
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "GET",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: sync-tool-with-job
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": {
      "type": "net.lookup",
      "operation": "dns"
    },
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: unavailable-without-reason
{
  "id": "protectpdf",
  "family": "docs",
  "name": "Protect PDF",
  "summary": "Put a password on a PDF (AES-256).",
  "status": "unavailable",
  "execution": "job",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/protectpdf/run",
    "job": {
      "type": "docs.process",
      "operation": "protect"
    }
  },
  "input": {
    "type": "object",
    "required": [
      "password"
    ],
    "properties": {
      "password": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128
      }
    }
  },
  "files": {
    "min": 1,
    "max": 1,
    "accept": [
      "application/pdf"
    ],
    "maxBytes": 104857600
  },
  "output": {
    "kind": "file",
    "mime": [
      "application/pdf"
    ]
  },
  "limits": {
    "timeoutMs": 300000,
    "maxPages": 2000
  },
  "auth": {
    "anonymous": false,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-job",
  "cost": 10,
  "egress": false,
  "hosts": [
    "protectpdf.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/protectpdf"
}
Rejected: unknown-capability
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.job.create"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: unknown-status
{
  "id": "dns",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "deprecated",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}
Rejected: uppercase-id
{
  "id": "DNS",
  "family": "net",
  "name": "DNS Lookup",
  "summary": "Look up A, AAAA, MX, TXT, NS, CNAME and other records for a domain.",
  "status": "stable",
  "execution": "sync",
  "api": true,
  "run": {
    "method": "POST",
    "path": "/api/v1/tools/dns/run",
    "job": null,
    "legacy": [
      "GET /api/net/dns/:target"
    ]
  },
  "input": {
    "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/input"
  },
  "files": null,
  "output": {
    "kind": "json",
    "schema": {
      "$ref": "https://openvibe.tools/api/v1/tools/dns/schema#/$defs/output"
    }
  },
  "limits": {
    "timeoutMs": 10000,
    "maxInputBytes": 1024,
    "perTargetPerMinute": 30
  },
  "auth": {
    "anonymous": true,
    "capability": "tools.tool.run"
  },
  "quotaClass": "tools-fetch",
  "cost": 1,
  "egress": true,
  "hosts": [
    "dns.openvibe.tools"
  ],
  "docs": "https://openvibe.tools/tool/dns"
}

Validate

const contracts = require('openvibe-contracts');
contracts.validate('tools.tool@1', value);   // { valid, errors: [{ path, message }] }