# URL Normalise — strict, and it tells you what it changed (`telyvar/url-normalise`) Actor

Says whether two URLs address the same thing, and names every change it made to decide. Malformed input is refused with a reason and a position instead of silently repaired: the standard parser deletes newlines, keeps a broken percent-escape as text, and encodes a raw space without saying so.

- **URL**: https://apify.com/telyvar/url-normalise.md
- **Developed by:** [Alessandro Raffa](https://apify.com/telyvar) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.20 / 1,000 item normaliseds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## 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

## URL Normalise — strict, and it tells you what it changed

Says whether two URLs address the same thing, and names every change it made to decide. Malformed input is refused with a reason and a position instead of silently repaired: the standard parser deletes newlines, keeps a broken percent-escape as text, and encodes a raw space without saying so.

Called as `web.url.normalise`. Part of [Telyvar](https://telyvar.com/c/url-normalise/).

### What it accepts

The URLs to normalise, and which meaning-changing normalisations you accept. Every item gets its own result: a bad URL is reported with a reason and a position, is not billed, and does not stop the ones after it.

| Field | Type | Meaning |
| --- | --- | --- |
| `items` | array | The URLs to normalise, in order. Results carry the index they came from. Absolute http and https only: a relative URL has no meaning without the base it is relative to, and this capability is not given one. At most 2,000 per call: at 50 ms an item that is 102 seconds, inside the platform's hard 300-second window for a synchronous call. A larger job is several calls, which is deliberate. |
| `also` | array | Seven normalisations that cannot change which resource is addressed are always applied. These five can. Dropping www addresses a different name and it may be a different server. Sorting the query reorders parameters a server may read in order. Dropping the fragment discards what a client-side application uses to route. Pick only what you can defend. |
| `compareTo` | string | Optional. One URL, normalised with the same settings. Every result then says whether it came out identical to it. If this one is not a URL, the run stops before charging anything: every comparison would otherwise be measured against nothing. |

### What you pay for

An item that fails is not billed, and one bad item does not stop the ones after it. Every item is charged as it is delivered, one at a time, so a spending limit stops the run at the limit rather than one batch past it.

### What would end it

The channel absorbs URL comparison as a free built-in, or a standard parser starts reporting what it repaired — at which point the difference this sells stops existing. Also the usual threshold: monthly revenue below the cost of keeping it running, two months running.

Written before it happens, on purpose. A capability that quietly stops being worth running costs its buyers more than one that says in advance how it ends.

***

Generated from this capability's own manifest by `tools/site/build.mjs`. Do not edit it by hand: the next build overwrites it, and `pnpm site:check` fails while it disagrees with the manifest.

# Actor input Schema

## `items` (type: `array`):

The URLs to normalise, in order. Results carry the index they came from. Absolute http and https only: a relative URL has no meaning without the base it is relative to, and this capability is not given one. At most 2,000 per call: at 50 ms an item that is 102 seconds, inside the platform's hard 300-second window for a synchronous call. A larger job is several calls, which is deliberate.

## `also` (type: `array`):

Seven normalisations that cannot change which resource is addressed are always applied. These five can. Dropping www addresses a different name and it may be a different server. Sorting the query reorders parameters a server may read in order. Dropping the fragment discards what a client-side application uses to route. Pick only what you can defend.

## `compareTo` (type: `string`):

Optional. One URL, normalised with the same settings. Every result then says whether it came out identical to it. If this one is not a URL, the run stops before charging anything: every comparison would otherwise be measured against nothing.

## Actor input object example

```json
{
  "items": [
    "HTTPS://EXAMPLE.com:443/a/./b/../c?b=2&a=1#top",
    "https://example.com/a/c?b=2&a=1#top",
    "https://example.com/%zz"
  ],
  "also": []
}
```

# Actor output Schema

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

One row per item in the order they were given, each carrying its index. A failed item is written here too, with the rule it broke and the position, so a caller can tell an item that could not be normalised from one that was never reached.

## `report` (type: `string`):

delivered, succeeded, failed, notAttempted and stoppedEarly. notAttempted above zero with stoppedEarly true is a complete answer, not a truncated one: the caller's spending limit stopped the work and the run says how many it never reached.

# 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 = {
    "items": [
        "HTTPS://EXAMPLE.com:443/a/./b/../c?b=2&a=1#top",
        "https://example.com/a/c?b=2&a=1#top",
        "https://example.com/%zz"
    ],
    "also": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("telyvar/url-normalise").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 = {
    "items": [
        "HTTPS://EXAMPLE.com:443/a/./b/../c?b=2&a=1#top",
        "https://example.com/a/c?b=2&a=1#top",
        "https://example.com/%zz",
    ],
    "also": [],
}

# Run the Actor and wait for it to finish
run = client.actor("telyvar/url-normalise").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 '{
  "items": [
    "HTTPS://EXAMPLE.com:443/a/./b/../c?b=2&a=1#top",
    "https://example.com/a/c?b=2&a=1#top",
    "https://example.com/%zz"
  ],
  "also": []
}' |
apify call telyvar/url-normalise --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,telyvar/url-normalise"
        }
    }
}
```

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/yVPQIkJClaleuBv0S/builds/vgr3WkzWRZhMmdTaY/openapi.json
