---
**📚 Main Documentation:** [Hoko API Documentation (llms.txt)](https://hoko.to/docs/llms.txt)
This is an individual endpoint documentation file. For the complete API reference, see the main documentation above.
---
# (POST) Create links
Create one or more links in a single request. Each link requires a destination URL and collection ID.
**Category:** Links
## Endpoint

POST /api/links

Create one or more links in a single request. This endpoint accepts an array of link objects, allowing you to create multiple links efficiently in one API call.

Each link requires a destination URL and a collection ID. A unique short ID is automatically generated for each link, producing a short URL and QR code URL under https://hoko.to.

You can include rich metadata such as titles, descriptions, images, UTM parameters, tag IDs, and custom external IDs to organize and track your links effectively.

**Endpoint**

```text
POST /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. Each object represents one link to create.

You can create 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.

| Parameter    | Type          | Required | Location | Description                                                                                              |
| ------------ | ------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------- |
| url          | string (URL)  | Yes      | body     | The destination URL that the short link will redirect to. Must be a valid HTTPS URL.                     |
| collectionId | string (UUID) | Yes      | body     | The ID of the collection (folder) to organize this link. The collection must exist in your workspace.    |
| tagIds       | string[]      | No       | body     | Existing tag IDs to assign to this link. Every tag must already exist in your workspace.                 |
| title        | string        | No       | body     | A descriptive title for the link. Useful for organization and identification.                            |
| description  | string        | No       | body     | Additional description or notes about the link.                                                          |
| image        | string (URL)  | No       | body     | A preview image URL for the link. Must be a valid HTTPS URL.                                             |
| utm          | object        | No       | body     | UTM parameters for campaign tracking. Create requests accept string values for source, medium, campaign, term, content, and referral. |
| expiresAt    | string        | No       | body     | ISO date-time when the link should expire. Requires a plan with link expiration.                         |
| expiredUrl   | string (URL)  | No       | body     | Destination used after expiration. Requires a plan with link expiration.                                 |
| password     | string        | No       | body     | Password required before resolving the destination. Requires a plan with password protection.            |
| cloaked      | boolean       | No       | body     | Render the resolved destination in an iframe. The destination is preflighted and rejected with 400 if it currently blocks iframe rendering. Requires a plan with link cloaking. |
| ios          | string (URL)  | No       | body     | Destination for iOS visitors. Requires a plan with device targeting.                                     |
| android      | string (URL)  | No       | body     | Destination for Android visitors. Requires a plan with device targeting.                                 |
| geo          | object/null   | No       | body     | Country-code destination map such as `{ "SA": "https://example.com/sa" }`; send null or omit for no geo targeting. Requires geo targeting when set. |
| externalId   | string        | No       | body     | A custom external ID for syncing with external systems. Must be unique within your workspace.            |
| tenantId     | string        | No       | body     | A tenant identifier for multi-tenant scenarios. Useful for segmenting links by customer or organization. |
| partnerId    | string (UUID) | No       | body     | The ID of the partner associated with this link. Used for affiliate and partner attribution tracking.    |

> **Warning: Plan Limits**
> The actual maximum number of links you can create 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.

## Advanced Link Behavior

Advanced fields are applied only when they are set on the link and your workspace plan supports the feature.

All URL fields accepted by this endpoint must use `https:`. This includes `url`, `image`, `expiredUrl`, `ios`, `android`, and every URL value in `geo`.

- `password`: Visitors must enter the password before Hoko resolves the final destination. A correct password stores a secure, HTTP-only cookie scoped to that short link, so the same visitor can continue without re-entering it.
- `expiresAt`: Use an ISO date-time with timezone offset, such as `2026-12-31T23:59:00Z`. When the current server time is later than `expiresAt`, the link is expired. If `expiredUrl` is set, Hoko sends visitors there. If it is not set, Hoko shows an expired-link page.
- `ios` and `android`: Device destinations are selected from the visitor's User-Agent. iOS and Android checks run before geo targeting.
- `geo`: Map ISO 3166-1 alpha-2 country codes to destination URLs, for example `{ "SA": "https://example.com/sa" }`. Use uppercase country codes. Send `null` or omit the field for no geo targeting. Country detection is best-effort and uses trusted edge country headers when available, then IP lookup.
- `cloaked`: Hoko renders the resolved destination in a full-page iframe. Create requests preflight the destination and return 400 if it currently blocks iframe rendering.

Resolution order is: password gate, expiration, iOS/Android targeting, geo targeting, default `url`, then cloaking. Expired links do not run device or geo targeting.

## Status Codes

### 201

Links created successfully.

**Request**

```bash
curl -X POST "https://hoko.to/api/links" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "url": "https://example.com",
      "collectionId": "550e8400-e29b-41d4-a716-446655440000",
      "tagIds": ["7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0"],
      "expiresAt": "2026-12-31T23:59:00Z",
      "expiredUrl": "https://example.com/expired",
      "password": "launch",
      "cloaked": true,
      "ios": "https://example.com/ios",
      "android": "https://example.com/android",
      "geo": {
        "SA": "https://example.com/sa"
      }
    }
  ]'
```

**Request**

```javascript
const response = await fetch('https://hoko.to/api/links', {
	method: 'POST',
	headers: {
		Authorization: 'Bearer <API_KEY>',
		'Content-Type': 'application/json'
	},
	body: JSON.stringify([
		{
			url: 'https://example.com',
			collectionId: '550e8400-e29b-41d4-a716-446655440000',
			tagIds: ['7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0'],
			expiresAt: '2026-12-31T23:59:00Z',
			expiredUrl: 'https://example.com/expired',
			password: 'launch',
			cloaked: true,
			ios: 'https://example.com/ios',
			android: 'https://example.com/android',
			geo: {
				SA: 'https://example.com/sa'
			}
		}
	])
});
const links = await response.json();
```

**Response**

```json
[
	{
		"id": "550e8400-e29b-41d4-a716-446655440000",
		"url": "https://example.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": null,
		"description": null,
		"image": null,
		"utm": null,
		"expiresAt": "2026-12-31T23:59:00Z",
		"expiredUrl": "https://example.com/expired",
		"password": "launch",
		"cloaked": true,
		"ios": "https://example.com/ios",
		"android": "https://example.com/android",
		"geo": {
			"SA": "https://example.com/sa"
		},
		"collectionId": "550e8400-e29b-41d4-a716-446655440000",
		"tagIds": [
			"7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0"
		],
		"externalId": null,
		"tenantId": null,
		"partnerId": null,
		"createdAt": "2024-01-01T00:00:00Z"
	}
]
```

### 400

Invalid request body (missing required fields or invalid values).

**Request**

```bash
curl -X POST "https://hoko.to/api/links" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "url": "invalid-url"
    }
  ]'
```

**Request**

```javascript
const response = await fetch('https://hoko.to/api/links', {
	method: 'POST',
	headers: {
		Authorization: 'Bearer <API_KEY>',
		'Content-Type': 'application/json'
	},
	body: JSON.stringify([
		{
			url: 'invalid-url'
		}
	])
});
```

**Response**

```json
{
	"error": {
		"en": "Invalid request",
		"ar": "طلب غير صالح"
	}
}
```

### 401

Invalid or missing API key.

**Request**

```bash
curl -X POST "https://hoko.to/api/links" \
  -H "Authorization: Bearer invalid_key"
```

**Request**

```javascript
const response = await fetch('https://hoko.to/api/links', {
	method: 'POST',
	headers: {
		Authorization: 'Bearer invalid_key'
	}
});
```

**Response**

```json
{
	"error": {
		"en": "Invalid API key",
		"ar": "مفتاح API غير صالح"
	}
}
```

### 403

API key does not have the required linksWrite scope.

**Request**

```bash
curl -X POST "https://hoko.to/api/links" \
  -H "Authorization: Bearer <API_KEY>"
```

**Request**

```javascript
const response = await fetch('https://hoko.to/api/links', {
	method: 'POST',
	headers: {
		Authorization: 'Bearer <API_KEY>'
	}
});
```

**Response**

```json
{
	"error": {
		"en": "Missing required scopes",
		"ar": "الصلاحيات المطلوبة مفقودة"
	},
	"missingScopes": ["linksWrite"]
}
```

### 429

Rate limit exceeded. Check X-RateLimit-\* headers for details.

**Request**

```bash
curl -X POST "https://hoko.to/api/links" \
  -H "Authorization: Bearer <API_KEY>"
```

**Request**

```javascript
const response = await fetch('https://hoko.to/api/links', {
	method: 'POST',
	headers: {
		Authorization: 'Bearer <API_KEY>'
	}
});
```

**Response**

```json
{
	"error": {
		"en": "Rate limit exceeded",
		"ar": "تم تجاوز حد المعدل"
	},
	"retryAfter": 60
}
```

### 500

Unexpected server error.

**Response**

```json
{
	"error": {
		"en": "Internal server error",
		"ar": "خطأ داخلي في الخادم"
	}
}
```

## Examples

**Request**

```bash
curl -X POST "https://hoko.to/api/links" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "url": "https://example.com",
      "title": "Example Link",
      "description": "An example link",
      "collectionId": "550e8400-e29b-41d4-a716-446655440000",
      "tagIds": ["7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0"],
      "utm": {
        "source": "google",
        "medium": "cpc",
        "campaign": "summer"
      }
    }
  ]'
```

**Request**

```javascript
const response = await fetch('https://hoko.to/api/links', {
	method: 'POST',
	headers: {
		Authorization: 'Bearer <API_KEY>',
		'Content-Type': 'application/json'
	},
	body: JSON.stringify([
		{
			url: 'https://example.com',
			title: 'Example Link',
			description: 'An example link',
			collectionId: '550e8400-e29b-41d4-a716-446655440000',
			tagIds: ['7c4e4a56-f3bb-43e9-9dc7-2f4bda07c8f0'],
			utm: {
				source: 'google',
				medium: 'cpc',
				campaign: 'summer'
			}
		}
	])
});
const links = await response.json();
```

**Response**

```json
[
	{
		"id": "550e8400-e29b-41d4-a716-446655440000",
		"url": "https://example.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": "Example Link",
		"description": "An example link",
		"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"
	}
]
```

> **Warning: Important Constraints**
> The collectionId must reference an existing collection in your workspace. Each tagIds value must reference an existing tag in your workspace. The externalId must be unique within your workspace if provided. Plan limits may restrict the number of links you can create per request and overall.

---

**Back to main documentation:** [Hoko API Documentation (llms.txt)](https://hoko.to/docs/llms.txt)