(PUT) Update/Upsert links

Update existing links or create new ones with upsert operations. Perfect for synchronization scenarios.

Endpoint

PUT /api/links

The PUT endpoint implements an upsert operation, allowing you to update existing links or create new ones in a single request. This is particularly useful for synchronization scenarios where you want to ensure links exist with specific properties.

If an id is provided in the request body, the endpoint updates the existing link with that ID. If the ID does not exist, the request fails. To create new links, omit the id field.

Returns the updated or created links in the same order as the input array, making it easy to correlate requests with responses in bulk operations.

Endpoint text
PUT /api/links

Authentication

Requires authentication with an API key that has the linksWrite scope.

Request Body

The request body must be an array of link objects. Include the id field to update an existing link, or omit it to create a new one.

You can upsert up to 10,000 links in a single request. However, the actual maximum may be lower based on your subscription plan limits and available resources.

ParameterTypeRequiredDescription
id (body)string (UUID)YesRequired when updating. Omit to create a new link. If provided, it must exist in your workspace or the request fails.
url (body)string (URL)NoHTTPS destination URL. Required when creating a new link (no id). Optional when updating.
collectionId (body)string (UUID)NoCollection ID. Required when creating a new link (no id). Optional when updating.
tagIds (body)string[]NoExisting tag IDs to assign. When updating, replaces tag assignments if provided. Send `[]` to clear all tags.
title (body)stringNoLink title. Only changes when provided.
description (body)stringNoLink description. Only changes when provided.
image (body)string (URL)NoHTTPS preview image URL. Only changes when provided.
utm (body)objectNoUTM parameters object. Insert entries accept string values; update entries may send null for individual keys to clear them.
expiresAt (body)string/nullNoISO date-time when the link should expire. Send null to clear. Requires link expiration when setting a value.
expiredUrl (body)string/nullNoHTTPS destination used after expiration. Send null to clear. Requires link expiration when setting a value.
password (body)string/nullNoPassword required before resolving the destination. Send null to clear. Requires password protection when set.
cloaked (body)boolean/nullNoRender the resolved destination in an iframe. Send false/null to clear. When true, the destination is preflighted and rejected with 400 if it currently blocks iframe rendering.
ios (body)string/nullNoHTTPS destination for iOS visitors. Send null to clear. Requires device targeting when setting a value.
android (body)string/nullNoHTTPS destination for Android visitors. Send null to clear. Requires device targeting when setting a value.
geo (body)object/nullNoUppercase country-code HTTPS destination map. Send null to clear on updates or omit/null on inserts for no geo targeting. Requires geo targeting when setting destinations.
externalId (body)stringNoExternal ID for syncing. Must be unique in workspace when provided.
tenantId (body)stringNoTenant identifier. Optional.
partnerId (body)string (UUID)NoPartner ID for attribution. Optional.

Plan Limits

The actual maximum number of links you can upsert per request may be lower than 10,000 based on your subscription plan limits and available resources. The system will enforce both the hard limit (10,000) and your plan-specific limits.

Examples

Request cURL
curl -X PUT "https://hoko.to/api/links" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "url": "https://updated.com",
      "title": "Updated Link",
      "collectionId": "550e8400-e29b-41d4-a716-446655440000",
      "tagIds": ["7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0"]
    }
  ]'
Request JavaScript
const response = await fetch('https://hoko.to/api/links', {
	method: 'PUT',
	headers: {
		Authorization: 'Bearer <API_KEY>',
		'Content-Type': 'application/json'
	},
	body: JSON.stringify([
		{
			id: '550e8400-e29b-41d4-a716-446655440000',
			url: 'https://updated.com',
			title: 'Updated Link',
			collectionId: '550e8400-e29b-41d4-a716-446655440000',
			tagIds: ['7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0']
		}
	])
});
const links = await response.json();
Response json
[
	{
		"id": "550e8400-e29b-41d4-a716-446655440000",
		"url": "https://updated.com",
		"shortUrl": "https://hoko.to/abc123",
		"qrCode": "https://hoko.to/qrcode?text=https%3A%2F%2Fhoko.to%2Fabc123%3Fqr%3D1&size=512&errorCorrection=H&foreground=000000&background=FFFFFF&margin=1&format=svg",
		"title": "Updated Link",
		"description": null,
		"image": null,
		"utm": null,
		"expiresAt": null,
		"expiredUrl": null,
		"password": null,
		"cloaked": false,
		"ios": null,
		"android": null,
		"geo": null,
		"collectionId": "550e8400-e29b-41d4-a716-446655440000",
		"tagIds": [
			"7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0"
		],
		"externalId": null,
		"tenantId": null,
		"partnerId": null,
		"createdAt": "2024-01-01T00:00:00Z"
	}
]

Upsert Behavior

When updating, include at least one mutable field besides id. Only the fields you include are changed and omitted fields keep their current values. To clear an optional field, send it explicitly as null on update entries. To replace tag assignments, send tagIds; to clear all tags, send tagIds as an empty array. When creating a new link (no id), url and collectionId are required and create validation matches POST.