{
"status": "error",
"status_code": 404,
"message": "Link not found"
}
created New resource
{
"status": "created",
"status_code": 201,
"message": "Short link created successfully",
"data": { ... }
}
duplicate Already exists
{
"status": "duplicate",
"status_code": 409,
"message": "Already shortened. Use GET /api/v1/links"
}
Field
Type
Always present
Notes
status
string
✓
Human-readable outcome — see Status Codes section below
status_code
integer
✓
Mirrors the HTTP status code
message
string
On errors & creates
Description of what happened or went wrong
data
object
On success
The resource or result — shape varies by endpoint
Deluge pattern: Always check response.get("status") — not a boolean field. On error, read response.get("message") for a plain-English description.
Status & Codes
The status string tells you the exact outcome. The status_code mirrors the HTTP status code. Use either in your code — both are always present.
Success statuses
status
HTTP
When returned
success
200
GET request returned data — /me, /links, /links/:code, /stats, /qr
created
201
New short link was created — POST /shorten first time
updated
200
Link was modified — PATCH /links/:code
deleted
200
Link was permanently deleted — DELETE /links/:code
Error statuses
status
HTTP
When returned
duplicate
409
POST /shorten called with a URL you've already shortened, or custom slug already taken. No data returned — use GET /api/v1/links to retrieve the existing link.
error
400
Bad request — missing or invalid parameter, URL blocked by spam filter
error
401
Unauthorized — missing or invalid API key
error
403
Forbidden — feature requires Pro plan, or link limit reached
error
404
Not found — link code doesn't exist or doesn't belong to your account
error
429
Rate limited — too many requests, slow down
error
500
Server error — something went wrong on our side
Handling in Deluge
response = invokeurl[ ... ];
status = response.get("status");
if(status == "created")
{
// New link — read response.get("data").get("short_url")
}
else if(status == "duplicate")
{
// Already exists — call GET /api/v1/links to find it
// response.get("message") tells you what happened
}
else if(status == "error")
{
// Something went wrong — log response.get("message")
info "Error: " + response.get("message");
}
Limits & Rate Limits
MinifyURL enforces limits to ensure fair usage and protect service availability.
FreeFree
Saved links10 total
Analytics historyLast 7 days
QR code generation✓
Custom aliases✓
API access✓
Pro★ Pro
Saved links100 total
Analytics historyLast 30 days
QR code generation✓
Custom aliases✓
API access✓
API Rate LimitsPer API key
Endpoint
Limit
Window
POST/api/v1/shorten
60 requests
Per hour
GET/api/v1/links
Unlimited
—
DEL/api/v1/links/:id
Unlimited
—
GET/api/v1/analytics/:id
Unlimited
—
GET/api/v1/me
Unlimited
—
Auth Rate LimitsBrute-force & abuse protection
Action
Limit
Window
Scope
Login attempts
10 attempts
15 min
Per email
Login attempts
20 attempts
15 min
Per IP
Forgot password
5 requests
1 hour
Per IP
Resend OTP
5 requests
1 hour
Per IP
Link creation (dashboard)
10 links
10 min
Per session
Feedback
5 submissions
1 hour
Per IP
OTP VerificationEmail signup verification
10 min
OTP expiry
5
Max wrong attempts
60 s
Resend cooldown
5 / hr
Resend rate limit per IP
429 Too Many Requests — Returned when any rate limit is exceeded. Wait for the window to reset and retry. Auth endpoints return 403 if an account or IP is temporarily blocked after repeated failures.
Account
GET/api/v1/meAccount & plan info▾
Returns your account details, Pro status, and current link usage vs your plan limit.
Shorten a URL. Send parameters as a JSON body. Query string params are accepted for legacy compatibility.
Field
Type
Notes
urlrequired
string
Destination URL. Must include https://
custom_slugoptionalPRO
string
Custom code e.g. launch → minifyurl.in/launch
expires_inoptionalPRO
integer
Days until expiry
passwordoptionalPRO
string
Visitors must enter this before redirecting
activate_atoptionalPRO
ISO 8601
Link inactive until this datetime
Duplicate behaviour: If you shorten the same URL again (no custom_slug), the API returns 409 duplicate — not a success. No data is returned. Use GET /api/v1/links to retrieve the existing link.
Basic
API_KEY = "mfy_xxxx";
body = Map();
body.put("url", "https://example.com/very/long/path");
response = invokeurl
[
url: "https://minifyurl.in/api/v1/shorten"
type: POST
headers: {"Authorization": "Bearer " + API_KEY, "Content-Type": "application/json"}
body: body.toString()
];
status = response.get("status");
if(status == "created")
{
info "New link: " + response.get("data").get("short_url");
}
else if(status == "duplicate")
{
info response.get("message"); // "Already shortened. Use GET /api/v1/links"
}
else
{
info "Error: " + response.get("message");
}
const res = await fetch('https://minifyurl.in/api/v1/links?limit=20',
{ headers: { 'Authorization': 'Bearer mfy_xxxx' } });
const { status, data } = await res.json();
if (status === 'success') data.links.forEach(l => console.log(l.short_url, l.hits));
r = requests.get('https://minifyurl.in/api/v1/links',
params={'limit': 20, 'search': 'my-link'},
headers={'Authorization': 'Bearer mfy_xxxx'}).json()
if r['status'] == 'success':
for link in r['data']['links']:
print(link['short_url'], link['hits'])
import base64, re
r = requests.get('https://minifyurl.in/api/v1/links/launch26/qr',
headers={'Authorization': 'Bearer mfy_xxxx'}).json()
if r['status'] == 'success':
b64 = re.sub(r'^data:image/png;base64,', '', r['data']['qr_data_url'])
with open('qr.png', 'wb') as f: f.write(base64.b64decode(b64))
Webhooks let you receive real-time HTTP notifications whenever someone clicks one of your short links. Instead of polling the stats API, your server gets a POST request the moment a click happens — perfect for Zapier, Make, or any custom automation tool.
Free plan included: Webhooks are available on all plans. Up to 5 webhooks per account. A webhook that fails 10 consecutive times is automatically disabled to protect your endpoint.
Click Payload
Every click fires a POST request to your endpoint with Content-Type: application/json and this body:
The test field is true only when sent from the Dashboard test button — useful for distinguishing real clicks from test pings in your handler.
Verifying the Signature
If you set a secret when creating a webhook, every request includes an X-MinifyURL-Signature header. Verify it on your server to confirm the request came from MinifyURL and wasn't tampered with.
Deluge note: Deluge does not natively support HMAC-SHA256 verification. The simplest approach is to use a hard-to-guess secret URL path instead of signature verification — e.g. https://your-server.com/webhook/a8f3k2p9.
// Deluge — HTTP function triggered by MinifyURL webhook
// No signature verification needed if you use a secret URL path
code = input.get("code");
country = input.get("country");
clicks = input.get("clicks"); // not in payload — fetch separately if needed
isTest = input.get("test");
if(isTest == false)
{
// Find the matching Deal by Short_Code and update click tracking
searchCriteria = "(Short_Code:equals:" + code + ")";
deals = zoho.crm.searchRecords("Deals", searchCriteria);
if(deals.size() > 0)
{
updateMap = Map();
updateMap.put("Last_Click_Country", country);
updateMap.put("Last_Click_At", input.get("clicked_at"));
zoho.crm.updateRecord("Deals", deals.get(0).get("id"), updateMap);
}
}
const crypto = require('crypto');
// Express handler
app.post('/webhook/minifyurl', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-minifyurl-signature'];
const secret = process.env.MINIFYURL_WEBHOOK_SECRET;
// Verify signature
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (sig !== expected) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body);
if (!payload.test) {
console.log(`Click on /${payload.code} from ${payload.country} via ${payload.browser}`);
// Your logic here
}
res.status(200).send('ok');
});
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = 'your-webhook-secret'
@app.route('/webhook/minifyurl', methods=['POST'])
def webhook():
sig = request.headers.get('X-MinifyURL-Signature', '')
expected = 'sha256=' + hmac.new(
SECRET.encode(), request.data, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
payload = request.json
if not payload.get('test'):
print(f"Click on /{payload['code']} from {payload['country']}")
# Your logic here
return 'ok', 200
Managing Webhooks via API
You can also manage webhooks programmatically using your session cookie (dashboard endpoints — not the v1 API key endpoints).
Content-Type: application/json
User-Agent: MinifyURL-Webhook/1.0
X-MinifyURL-Event: click
X-MinifyURL-Delivery: a3f9b2c1d4e5f6a7 (random ID per delivery)
X-MinifyURL-Signature: sha256=... (only if secret is set)
Retry behaviour: Webhooks are fired once per click with a 5-second timeout. There is no automatic retry — if your endpoint is down, the delivery is lost. Your endpoint should return any 2xx status to be counted as a success. After 10 consecutive non-2xx responses, the webhook is automatically disabled.