Campaign URL Builder
marketing.utm_build
Overview
You send a link and the campaign parameters you want on it, and we hand back the tagged URL. Only the nine campaign parameter names analytics products read are accepted, so a mistyped name comes back as an error instead of disappearing into your warehouse as a missing channel. Nothing is invented: every parameter emitted is one you sent. If the link already carries one of them you decide what happens — refuse, replace, keep what is there, or add a second occurrence beside it. Path, existing query and fragment are copied character for character, so a link that is already encoded cannot come back double-encoded, and we never fetch, resolve or canonicalise anything.
Campaign links are assembled by hand, in spreadsheets and in templates, and every one of those quietly accepts a mistyped parameter name or an encoder that writes a plus sign where a percent escape belongs. You get one encoding, one order, one closed name list, and an error when something does not fit.
Reliability
Fully deterministic: the same link and parameters give you the same string, on any machine, under any locale or time zone. Failures, and what to do about each: UTM_BUILD_URL_INVALID means the link is not a well-formed absolute URL with a host, or carries a space, a control character or surrounding whitespace, so clean it up and send it again; UTM_BUILD_URL_NOT_ABSOLUTE means there is no scheme, and we resolve nothing against a base, so send the full link; UTM_BUILD_URL_SCHEME_UNSUPPORTED means the scheme is neither http nor https, and a campaign parameter means nothing on a mail or file link; UTM_BUILD_URL_USERINFO_FORBIDDEN means the authority carries credentials, which we refuse rather than copy into a tagged link that ends up in an analytics report; UTM_BUILD_URL_TOO_LARGE means the link you sent is past 8192 code points, so shorten it before tagging; UTM_BUILD_PARAMS_INVALID means campaignParams is empty, and there would be nothing to add, so send at least one entry; UTM_BUILD_PARAM_NAME_UNSUPPORTED means a name is not one of the nine campaign parameters, which is usually a typo worth fixing at the source; UTM_BUILD_PARAM_VALUE_INVALID means a value is not a string or runs past 1024 code points, so send it as a shorter string; UTM_BUILD_PARAM_LIMIT_EXCEEDED means you sent more than 32 entries, which is more campaign parameters than exist, so drop the extras; UTM_BUILD_PARAM_CONFLICT means the link already carries a parameter you sent and the policy is FAIL, so pick REPLACE, KEEP_EXISTING or APPEND and retry; UTM_BUILD_POLICY_UNSUPPORTED means conflictPolicy or emptyValuePolicy is not one of the listed members, and we never quietly fall back to a default; UTM_BUILD_QUERY_LIMIT_EXCEEDED means the link already carries more than 500 query parameters, or the query we would write runs past 8192 characters, so trim the query first; UTM_BUILD_OUTPUT_TOO_LARGE means the tagged link would run past 8192 code points, so shorten the values or the link. None of these is worth retrying unchanged.
Context footprint
Returns the tagged link, a short list per parameter we wrote or skipped, and counters. Your values come back as lengths, not as text. A successful call costs 1 execution credit.
When to use it
When you have a link and the campaign values you want on it, and you want the same bytes out every time.
When you need an arbitrary query parameter, a sorted signing query, or the campaign values read back off an existing link.
Scope
Non-goals
- Invents no parameter value — no default source, medium or campaign id
- Does not sort the query; signing wants sorted keys and that belongs to network.build_query_string
- Does not canonicalise the link — host case, port, dot segments and existing escapes are left exactly as they arrived
- Does not fetch, resolve or check the link
- Does not accept a credential anywhere, including in the authority
- Does not generate a slug or shorten a link
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Absolute http or https link to tag, up to 8192 code points, with a host and no credentials. |
| campaignParams | object | Yes | Closed map of 1 to 9 campaign parameters from the published utm_* name list, each a string of 0 through 1024 code points. |
| conflictPolicy | string | No | What to do when the link already carries a campaign parameter you sent; omit it for FAIL. |
| emptyValuePolicy | string | No | Whether a blank value is written as name= or skipped; omit it for EMIT. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Tagged link after campaign parameters were encoded and appended, with everything else copied through unchanged. |
| added | array | Yes | One object per parameter written, with name, position, and valueLength rather than the value itself. |
| conflicts | array | Yes | One object per campaign parameter the link already carried; empty when nothing clashed. |
| skipped | array | Yes | Entries not written because KEEP_EXISTING or OMIT applied; empty unless a policy left something out. |
| encoding | object | Yes | Closed encoding receipt with space encoding, hex case, and the unreserved character set, so a stored link can be rebuilt byte for byte. |
| summary | object | Yes | Closed counters for query parameter count, how many parameters were added or clashed, reserved characters encoded, and tagged URL length. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"marketing.utm_build","input":{"url":"https://acme.example.com/pricing","campaignParams":{"utm_source":"newsletter","utm_medium":"email"}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("marketing.utm_build", {"campaignParams":{"utm_source":"newsletter","utm_medium":"email","utm_campaign":"spring sale"},"url":"https://acme.example.com/pricing?plan=team"});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| UTM_BUILD_URL_INVALID | 400 | No | The request payload failed this skill's input validation. |
| UTM_BUILD_URL_NOT_ABSOLUTE | 400 | No | The request payload failed this skill's input validation. |
| UTM_BUILD_URL_SCHEME_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| UTM_BUILD_URL_USERINFO_FORBIDDEN | 422 | No | The input was rejected as unsafe. |
| UTM_BUILD_URL_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| UTM_BUILD_PARAMS_INVALID | 400 | No | The request payload failed this skill's input validation. |
| UTM_BUILD_PARAM_NAME_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| UTM_BUILD_PARAM_VALUE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| UTM_BUILD_PARAM_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| UTM_BUILD_PARAM_CONFLICT | 412 | No | The input is valid but cannot be processed as-is. |
| UTM_BUILD_POLICY_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| UTM_BUILD_QUERY_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| UTM_BUILD_OUTPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Invents no parameter value — no default source, medium or campaign id
- Does not sort the query; signing wants sorted keys and that belongs to network.build_query_string
- Does not canonicalise the link — host case, port, dot segments and existing escapes are left exactly as they arrived
- Does not fetch, resolve or check the link
- Does not accept a credential anywhere, including in the authority
- Does not generate a slug or shorten a link
- Adding an arbitrary query parameter that is not a campaign parameter
- Building a signed query where the key order has to be sorted
- Reading the campaign parameters back off a link
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "marketing.utm_build",
"input": {
"campaignParams": {
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "spring sale"
},
"url": "https://acme.example.com/pricing?plan=team"
}
}Examples
Tag a pricing page for a newsletter campaign
{
"campaignParams": {
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "spring sale"
},
"url": "https://acme.example.com/pricing?plan=team"
}{
"encoding": {
"spaceEncoding": "percent_20",
"hexCase": "upper",
"unreserved": "ALPHA DIGIT - . _ ~"
},
"skipped": [],
"summary": {
"reservedCharactersEncoded": 1,
"urlLength": 108,
"conflictCount": 0,
"addedCount": 3,
"queryParameterCount": 4
},
"added": [
{
"name": "utm_source",
"position": 1,
"valueLength": 10
},
{
"name": "utm_medium",
"position": 2,
"valueLength": 5
},
{
"name": "utm_campaign",
"position": 3,
"valueLength": 11
}
],
"url": "https://acme.example.com/pricing?plan=team&utm_source=newsletter&utm_medium=email&utm_campaign=spring%20sale",
"conflicts": []
}Replace a campaign source the link already carries
{
"url": "https://acme.example.com/launch?utm_source=old-list",
"conflictPolicy": "REPLACE",
"campaignParams": {
"utm_source": "spring-list"
}
}{
"encoding": {
"spaceEncoding": "percent_20",
"hexCase": "upper",
"unreserved": "ALPHA DIGIT - . _ ~"
},
"skipped": [],
"summary": {
"reservedCharactersEncoded": 0,
"urlLength": 54,
"conflictCount": 1,
"addedCount": 1,
"queryParameterCount": 1
},
"added": [
{
"name": "utm_source",
"position": 0,
"valueLength": 11
}
],
"url": "https://acme.example.com/launch?utm_source=spring-list",
"conflicts": [
{
"name": "utm_source",
"existingOccurrences": 1,
"policyApplied": "REPLACE"
}
]
}Recommended use cases
- Tag a landing page for a newsletter or paid campaign with a stable, reproducible encoding
- Generate hundreds of campaign links from one template without a spreadsheet quietly mistyping utm_source
- Add a campaign parameter to a link that already carries a query, without touching what is there
Avoid for
- Adding an arbitrary query parameter that is not a campaign parameter
- Building a signed query where the key order has to be sorted
- Reading the campaign parameters back off a link
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.