# Hoko Hoko is a link attribution platform for creating branded short links, tracking clicks, and connecting leads and sales to the campaigns that produced them. ## Primary Machine-Readable Resources - Full LLM context: https://hoko.to/llms-full.txt - OpenAPI: https://hoko.to/openapi.json - MCP endpoint: https://hoko.to/mcp - API LLM context: https://hoko.to/api/llms.txt - API full context: https://hoko.to/api/llms-full.txt - Docs LLM context: https://hoko.to/docs/llms.txt - Help LLM context: https://hoko.to/help/llms.txt - Sitemap index: https://hoko.to/sitemap.xml ## API Endpoint Inventory | Method | Path | Scope | Purpose | | --- | --- | --- | --- | | GET | /api/links | linksRead | List links by id, externalId, tenantId, partnerId, and pagination parameters. | | POST | /api/links | linksWrite | Create one or more short links. | | PUT | /api/links | linksWrite | Create or update links; entries with id update, entries without id insert. | | DELETE | /api/links | linksWrite | Soft-delete one or more links by UUID. | | GET | /api/collections | collectionsRead | List collections by id with pagination. | | POST/PUT/DELETE | /api/collections | collectionsWrite | Create, upsert, or soft-delete collections. | | GET | /api/tags | tagsRead | List tags by id with pagination. | | POST/PUT/DELETE | /api/tags | tagsWrite | Create, upsert, or delete tags. | | GET | /api/partners | partnersRead | List partners by id with pagination. | | POST/PUT/DELETE | /api/partners | partnersWrite | Create, upsert, or soft-delete partners. | | GET | /api/customers | customersRead | List customers by id or externalId with pagination. | | GET | /api/analytics | analyticsRead | List click analytics by linkId, partnerId, startDate, endDate, and pagination parameters. | | POST | /api/track/lead | conversionsWrite | Record a lead conversion and upsert the customer. | | POST | /api/track/sale | conversionsWrite | Record a sale conversion and upsert the customer. | ## Documentation ### Getting Started - [Introduction](https://hoko.to/docs/introduction) Hoko is a modern link attribution platform that empowers you to create branded short links, track conversions end-to-end, and measure marketing performance with precision. - [API Keys](https://hoko.to/docs/api-keys) Create and manage API keys with granular permission scopes. Secure your integrations with the principle of least privilege. - [Authentication](https://hoko.to/docs/authentication) Secure your API requests with Bearer token authentication. All endpoints require a valid API key in the Authorization header. - [Rate Limits](https://hoko.to/docs/rate-limits) Understand rate limits, monitor your usage, and implement proper retry logic to ensure reliable API access. - [Errors](https://hoko.to/docs/errors) Understand error responses, status codes, and how to handle API errors gracefully in your applications. ### Links - [Introduction](https://hoko.to/docs/links/introduction) The Links API is the foundation of Hoko, enabling you to create, manage, and track short links programmatically. - [(GET) Get links](https://hoko.to/docs/links/get) Retrieve links from your workspace with flexible filtering options. Filter by link ID, external ID, tenant ID, or partner ID. - [(POST) Create links](https://hoko.to/docs/links/post) Create one or more links in a single request. Each link requires a destination URL and collection ID. - [(PUT) Update/Upsert links](https://hoko.to/docs/links/put) Update existing links or create new ones with upsert operations. Perfect for synchronization scenarios. - [(DELETE) Delete links](https://hoko.to/docs/links/delete) Delete one or more links using soft delete. Historical analytics data is preserved. ### Collections - [Introduction](https://hoko.to/docs/collections/introduction) Organize your links with collections. Collections are essential organizational units that help you structure and manage your link library. - [(GET) Get collections](https://hoko.to/docs/collections/get) Retrieve collections from your workspace. Filter by collection ID or list all collections with pagination. - [(POST) Create collections](https://hoko.to/docs/collections/post) Create one or more collections to organize your links. Each collection requires a name. - [(PUT) Update/Upsert collections](https://hoko.to/docs/collections/put) Update existing collections or create new ones with upsert operations. - [(DELETE) Delete collections](https://hoko.to/docs/collections/delete) Delete one or more collections using soft delete. ### Tags - [Introduction](https://hoko.to/docs/tags/introduction) Create, read, update, and delete tags for categorizing and organizing your links. - [(GET) Get tags](https://hoko.to/docs/tags/get) Retrieve tags from your workspace. Filter by tag ID or list all tags with pagination. - [(POST) Create tags](https://hoko.to/docs/tags/post) Create one or more tags. Each tag requires a name, and color is optional. - [(PUT) Update/Upsert tags](https://hoko.to/docs/tags/put) Update existing tags or create new ones with upsert operations. - [(DELETE) Delete tags](https://hoko.to/docs/tags/delete) Permanently delete one or more tags (hard delete). ### Analytics - [Introduction](https://hoko.to/docs/analytics/introduction) Retrieve click analytics and performance data for your links. Gain insights into traffic patterns, user behavior, and campaign effectiveness. - [(GET) Get analytics](https://hoko.to/docs/analytics/get) Retrieve click analytics and performance data for your links. Filter by link, partner, or date range. ### Customers - [Introduction](https://hoko.to/docs/customers/introduction) Read customer data. Customer creation and updates are handled automatically through conversion tracking endpoints. - [(GET) Get customers](https://hoko.to/docs/customers/get) Retrieve customers by ID or external ID. Supports pagination and sorting. ### Partners - [Introduction](https://hoko.to/docs/partners/introduction) Create, read, update, and delete partners for attribution tracking and commission management. - [(GET) Get partners](https://hoko.to/docs/partners/get) Retrieve partners by ID. Supports pagination and sorting. - [(POST) Create partners](https://hoko.to/docs/partners/post) Create one or more partners. Each partner requires a name. - [(PUT) Update/Upsert partners](https://hoko.to/docs/partners/put) Update existing partners or create new ones with upsert operations. - [(DELETE) Delete partners](https://hoko.to/docs/partners/delete) Delete one or more partners using soft delete. ### Conversion Tracking - [Introduction](https://hoko.to/docs/track/introduction) Track lead and sale conversion events to connect marketing touchpoints to business outcomes. Automatically create and update customer records with flexible attribution tracking. - [Browser attribution script (analytics.js)](https://hoko.to/docs/track/analytics-js) Persist the clickId from short-link redirects using analytics.js and send it to conversion endpoints. - [Embedded click tracking](https://hoko.to/docs/track/embedded-click) Track a Hoko short-link click from an owned page without redirecting the visitor. - [Track lead events](https://hoko.to/docs/track/lead) - [Track sale events](https://hoko.to/docs/track/sale) ## Help ### Help - [Getting started](https://hoko.to/help/getting-started) - [Profile](https://hoko.to/help/profile) - [Workspaces](https://hoko.to/help/workspaces) - [Collections](https://hoko.to/help/collections) - [Links](https://hoko.to/help/links) - [Tags](https://hoko.to/help/tags) - [Partners](https://hoko.to/help/partners) - [UTM templates](https://hoko.to/help/utm-templates) - [Analytics](https://hoko.to/help/analytics) - [Participants](https://hoko.to/help/participants) - [API keys](https://hoko.to/help/api-keys) - [Billing](https://hoko.to/help/billing) - [Storage](https://hoko.to/help/storage) - [Security](https://hoko.to/help/security) ## Full API Documentation ### Introduction Hoko is a modern link attribution platform that empowers you to create branded short links, track conversions end-to-end, and measure marketing performance with precision. Canonical URL: https://hoko.to/docs/introduction Markdown URL: https://hoko.to/docs/introduction/README.md ## What is Hoko? Hoko is a comprehensive link attribution and conversion tracking platform designed for modern marketing teams. Whether you're running multi-channel campaigns, tracking affiliate performance, or measuring ROI, Hoko provides the tools you need to understand exactly how your marketing efforts drive results. Built with developers in mind, Hoko offers a powerful REST API that integrates seamlessly with your existing stack, enabling automated link management, real-time analytics, and sophisticated conversion tracking. ## Key Features Hoko combines powerful link management with advanced analytics and conversion tracking. Here's what makes it special: - Branded Short Links - Create memorable short URLs with your own domain for enhanced trust and brand recognition - Real-Time Analytics - Track clicks, conversions, and user behavior with detailed insights updated in real-time - Conversion Tracking - Connect clicks to leads and sales with flexible event tracking and customer attribution - REST API - Full programmatic access to all features with comprehensive endpoints for automation and integrations - Multi-Channel Attribution - Track performance across campaigns, partners, and channels with granular UTM parameter support - Partner Management - Organize and track affiliate partners with commission and performance analytics - Collections & Tags - Organize links efficiently with collections and tags for better campaign management - Customer Insights - Build comprehensive customer profiles by tracking their journey from click to conversion ## Use Cases Hoko is perfect for a wide range of marketing and attribution scenarios: - Marketing Campaigns - Track performance of email, social media, and paid advertising campaigns - Affiliate Programs - Manage partner links and track commission-based conversions - E-commerce Attribution - Connect marketing touchpoints to actual sales and revenue - Lead Generation - Track which channels and campaigns generate the highest-quality leads - Content Marketing - Measure the impact of blog posts, newsletters, and content shares - Multi-Tenant SaaS - Track links and conversions across different customer segments or tenants - API Integrations - Build custom integrations with CRM, marketing automation, and analytics tools ## Getting Started Ready to get started? Here's a quick overview of what you'll need: - Create a Hoko account and set up your workspace - Create your first collection to organize links - Generate an API key with appropriate scopes for your use case - Start creating links via the dashboard or API - Track conversions by integrating the conversion tracking endpoints > **Tip: Pro Tip** > Start with the Links section to understand how to create and manage short links, then explore Analytics to see how to track performance, and finally dive into Conversion Tracking to connect clicks to business outcomes. --- ### API Keys Create and manage API keys with granular permission scopes. Secure your integrations with the principle of least privilege. Canonical URL: https://hoko.to/docs/api-keys Markdown URL: https://hoko.to/docs/api-keys/README.md ## Overview API keys are the primary method of authenticating requests to the Hoko API. Each key is associated with your workspace and can be configured with specific permission scopes, allowing you to control exactly what operations each key can perform. This granular access control enables you to create different keys for different purposes—for example, a read-only key for analytics dashboards, or a write key for automated link creation scripts. ## Creating API Keys Create new API keys from your workspace dashboard under Settings > Integrations > API Keys. When creating a key, you'll need to: 1. Provide a descriptive name to help you identify the key's purpose 2. Select the appropriate permission scopes based on what operations the key needs to perform 3. Copy the key immediately after creation—it will only be displayed once for security reasons > **Warning: Important Security Notice** > API keys are displayed only once during creation. If you lose a key, you must revoke it and create a new one. Never share API keys publicly or commit them to version control. ## Permission Scopes Scopes define what operations an API key can perform. By granting only the minimum required permissions, you follow security best practices and limit potential damage if a key is compromised. Each scope is independent—you can combine multiple scopes to create keys with exactly the permissions you need. - linksRead - Read and list links (GET /api/links) - linksWrite - Create, update, and delete links (POST, PUT, DELETE /api/links) - collectionsRead - Read and list collections (GET /api/collections) - collectionsWrite - Create, update, and delete collections (POST, PUT, DELETE /api/collections) - tagsRead - Read and list tags (GET /api/tags) - tagsWrite - Create, update, and delete tags (POST, PUT, DELETE /api/tags) - analyticsRead - Access click analytics and performance data (GET /api/analytics) - conversionsWrite - Track lead and sale conversion events (POST /api/track/lead, POST /api/track/sale) - customersRead - Read customer data (GET /api/customers) - partnersRead - Read partner data (GET /api/partners) - partnersWrite - Create, update, and delete partners (POST, PUT, DELETE /api/partners) > **Tip: Best Practice** > Create separate API keys for different environments (development, staging, production) and different use cases (read-only monitoring, automated link creation, conversion tracking). This makes it easier to rotate keys and audit access. ## Using API Keys Once you have an API key, include it in every API request using the Authorization header with the Bearer scheme. The API will validate the key and check that it has the required scopes for the requested operation. **Header Format** ```text Authorization: Bearer ``` **curl Example** ```bash curl -X GET "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" ``` ## Revoking API Keys You can revoke API keys at any time from your workspace settings. Revocation is immediate and permanent—revoked keys cannot be restored or reactivated. Revoke keys immediately if you suspect they've been compromised, or when they're no longer needed. This is a critical security practice, especially when keys are used in production environments. > **Error: Security Alert** > If an API key is exposed or compromised, revoke it immediately and create a new one. Monitor your API usage logs for any suspicious activity after key revocation. --- ### Authentication Secure your API requests with Bearer token authentication. All endpoints require a valid API key in the Authorization header. Canonical URL: https://hoko.to/docs/authentication Markdown URL: https://hoko.to/docs/authentication/README.md ## Overview Hoko uses Bearer token authentication for all API requests. This industry-standard approach ensures secure access to your workspace data while keeping implementation simple. Every API request must include a valid API key in the Authorization header. API keys are scoped to specific permissions, allowing you to follow the principle of least privilege. ## Bearer Token Authentication Include your API key in every request using the Authorization header with the Bearer scheme. The API key should be placed immediately after "Bearer " (note the space). Both the full format "Bearer " and just the API key itself are accepted for convenience. **Header Format** ```text Authorization: Bearer ``` **curl Example** ```bash curl -X GET "https://hoko.to/api/links" \ -H "Authorization: Bearer " ``` **JavaScript (fetch)** ```javascript fetch('https://hoko.to/api/links', { headers: { "Authorization": "Bearer ", "Content-Type": "application/json" } }) ``` **Python (requests)** ```python import requests headers = { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' } response = requests.get('https://hoko.to/api/links', headers=headers) ``` > **Warning: Security Best Practice** > Never expose your API keys in client-side code, public repositories, or shared documents. Always store API keys securely using environment variables or secret management services. ## Authentication Errors When authentication fails, the API returns a 401 Unauthorized status code with a descriptive error message. Understanding these errors helps you troubleshoot authentication issues quickly. - Missing Authorization header - The request doesn't include an Authorization header - Invalid API key format - The API key format is incorrect or malformed - Invalid or revoked key - The API key doesn't exist, has been revoked, or belongs to a different workspace - Expired key - The API key has expired (if expiration is configured) **Error Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` > **Info: Troubleshooting** > If you receive a 401 error, verify that your API key is correct, hasn't been revoked, and is included in the Authorization header. Check your workspace settings to ensure the key is active and has the required scopes. --- ### Rate Limits Understand rate limits, monitor your usage, and implement proper retry logic to ensure reliable API access. Canonical URL: https://hoko.to/docs/rate-limits Markdown URL: https://hoko.to/docs/rate-limits/README.md ## Overview Rate limiting protects the API from abuse and ensures fair usage across all workspaces. All API requests are rate limited per workspace based on your subscription plan. Rate limits are applied using a sliding window algorithm per minute. This means requests are counted within a rolling 60-second window, providing smooth and predictable rate limiting behavior. Each plan tier has different rate limits to accommodate various usage patterns, from small projects to enterprise-scale operations. > **Info: Rate Limit Scope** > Rate limits are applied per workspace, not per API key. All API keys within the same workspace share the same rate limit pool. ## Rate Limit Headers Every API response includes rate limit information in HTTP headers, allowing you to monitor your usage and implement intelligent retry logic. These headers are always present, even when the request is successful. Use these headers to build rate limit-aware applications that can proactively manage their API usage. - X-RateLimit-Limit - The maximum number of requests allowed per minute for your plan tier - X-RateLimit-Remaining - The number of requests remaining in the current window - X-RateLimit-Reset - Unix timestamp (in seconds) indicating when the current rate limit window resets **Response Headers** ```text // Example response headers X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 987 X-RateLimit-Reset: 1704067200 ``` > **Tip: Implementation Tip** > Monitor the X-RateLimit-Remaining header and implement exponential backoff when it approaches zero. This prevents hitting rate limits and ensures smooth API usage. ## Handling Rate Limit Exceeded When you exceed your rate limit, the API returns a 429 Too Many Requests status code. The response includes detailed information to help you implement proper retry logic. The retryAfter field indicates the number of seconds you should wait before retrying the request. This value is calculated based on when the current rate limit window resets. **429 Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 45 } ``` > **Warning: Best Practice** > Always respect the retryAfter value and implement exponential backoff. Avoid aggressive retry loops that could further impact your rate limit. Consider implementing request queuing for high-volume scenarios. ## Best Practices To maximize API reliability and avoid rate limit issues, follow these best practices: - Monitor rate limit headers in every response to track your usage - Implement exponential backoff when approaching rate limits - Use batch endpoints when available to reduce the number of requests - Cache responses when appropriate to minimize API calls - Consider upgrading your plan if you consistently approach rate limits - Implement request queuing for high-volume operations - Use webhooks for real-time updates instead of polling when possible --- ### Errors Understand error responses, status codes, and how to handle API errors gracefully in your applications. Canonical URL: https://hoko.to/docs/errors Markdown URL: https://hoko.to/docs/errors/README.md ## Overview The Hoko API uses standard HTTP status codes to indicate the result of API requests. All error responses follow a consistent, predictable format that makes error handling straightforward in your applications. Error messages are provided in both English and Arabic, allowing you to display user-friendly error messages regardless of your application's language preference. Understanding error responses helps you build robust integrations that gracefully handle edge cases and provide clear feedback to users. ## HTTP Status Codes The API uses standard HTTP status codes to communicate request outcomes. Familiarize yourself with these codes to implement proper error handling: - 400 Bad Request - The request is malformed, contains invalid parameters, or violates API constraints. Check your request format and parameters. - 401 Unauthorized - Authentication failed. The API key is missing, invalid, expired, or revoked. Verify your API key is correct and active. - 403 Forbidden - The API key is valid but lacks the required permission scopes for this operation. Check the missingScopes field in the response. - 404 Not Found - Returned for unknown endpoints. Resource validation errors are reported as 400 Bad Request. - 429 Too Many Requests - Rate limit exceeded. Wait for the retryAfter period before making additional requests. - 500 Internal Server Error - An unexpected server error occurred. These are rare; if you encounter this, contact support with request details. > **Info: Error Handling Strategy** > Implement comprehensive error handling that checks status codes and displays appropriate messages to users. For 4xx errors, the issue is typically with the request. For 5xx errors, retry with exponential backoff. ## Error Response Format All error responses follow a consistent structure. The error object contains bilingual messages, and additional fields provide context-specific information depending on the error type. **Error Format** ```json { "error": { "en": "Error message in English", "ar": "رسالة خطأ بالعربية" }, "missingScopes": ["linksWrite"], // Only for 403 errors "retryAfter": 45 // Only for 429 errors } ``` > **Tip: Error Message Selection** > Use the appropriate language field (en or ar) from the error object based on your application's language preference or user settings. ## Error Examples Here are examples of common error responses you might encounter: **400 Bad Request** ```json { "error": { "en": "Invalid request format", "ar": "تنسيق طلب غير صالح" } } ``` **401 Unauthorized** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` **403 Forbidden** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["linksWrite", "collectionsWrite"] } ``` **429 Too Many Requests** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 45 } ``` --- ### Introduction The Links API is the foundation of Hoko, enabling you to create, manage, and track short links programmatically. Canonical URL: https://hoko.to/docs/links/introduction Markdown URL: https://hoko.to/docs/links/introduction/README.md ## Overview Links are the core building blocks of Hoko. Every short link you create becomes a trackable touchpoint that connects your marketing efforts to business outcomes. The Links API provides full CRUD operations (Create, Read, Update, Delete) for managing your link library. Create branded short URLs, organize them with collections, attach UTM parameters for campaign tracking, and leverage partner attribution for affiliate programs. Each link automatically generates a unique short ID and branded short URL, making it easy to share and track across all your marketing channels. Links support rich metadata including titles, descriptions, images, and custom external IDs for seamless integration with your existing systems. - (GET) Retrieve links with flexible filtering by ID, external ID, tenant, or partner - (POST) Create one or more links with rich metadata and UTM parameters - (PUT) Update existing links or create new ones with upsert operations - (DELETE) Soft delete links while preserving historical analytics data > **Tip: Getting Started** > Before creating links, ensure you have at least one collection in your workspace. Every link must belong to a collection for organization. Use externalId to maintain references to links in your own system for seamless synchronization. --- ### (GET) Get links Retrieve links from your workspace with flexible filtering options. Filter by link ID, external ID, tenant ID, or partner ID. Canonical URL: https://hoko.to/docs/links/get Markdown URL: https://hoko.to/docs/links/get/README.md ## Endpoint GET /api/links Retrieve links from your workspace with flexible filtering options. Filter by link ID, external ID, tenant ID, or partner ID to find exactly what you need. The endpoint supports efficient pagination and sorting, making it easy to browse large link libraries. Results are returned as an array of link objects, each containing complete link information including the generated short URL and QR code URL. **Endpoint** ```text GET /api/links ``` ## Authentication Requires authentication with an API key that has the linksRead scope. ## Query Parameters All parameters are optional. Use them to filter and paginate results. | Parameter | Type | Required | Location | Description | | ---------- | ------------- | -------- | -------- | ------------------------------------------------------------------------------------------- | | id | string (UUID) | No | query | Filter by specific link ID. Returns a single link if found. | | externalId | string | No | query | Filter by external ID. Useful for syncing with external systems. | | tenantId | string | No | query | Filter by tenant ID for multi-tenant scenarios. | | partnerId | string (UUID) | No | query | Filter by partner ID to retrieve all links associated with a specific partner. | | take | number | No | query | Number of results to return (default: 50, min: 1, max: 10,000). | | skip | number | No | query | Number of results to skip for pagination (default: 0, min: 0). | | sort | string | No | query | Sort order by createdAt: "asc" for oldest first, "desc" for newest first (default: "desc"). | > **Warning: Request Limits** > The `take` parameter supports values from 1 to 10,000. Requests above 10,000 are rejected. ## Status Codes ### 200 Successfully retrieved links. **Request** ```bash curl -X GET "https://hoko.to/api/links?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links?take=50&sort=desc', { method: 'GET', headers: { Authorization: 'Bearer ' } }); 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": "https://example.com/image.jpg", "expiresAt": null, "expiredUrl": null, "password": null, "cloaked": false, "ios": null, "android": null, "geo": null, "createdAt": "2024-01-01T00:00:00Z" } ] ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X GET "https://hoko.to/api/links" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'GET', headers: { Authorization: 'Bearer invalid_key' } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required linksRead scope. **Request** ```bash curl -X GET "https://hoko.to/api/links" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'GET', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["linksRead"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-\* headers for details. **Request** ```bash curl -X GET "https://hoko.to/api/links" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'GET', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples Here are practical examples of retrieving links: **Request** ```bash curl -X GET "https://hoko.to/api/links?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links?take=50&sort=desc', { method: 'GET', headers: { Authorization: 'Bearer ' } }); 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": "https://example.com/image.jpg", "utm": { "source": "google", "medium": "cpc", "campaign": "summer", "term": "keyword", "content": "ad_content" }, "collectionId": "550e8400-e29b-41d4-a716-446655440000", "externalId": "ext_123", "tenantId": "tenant_123", "partnerId": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2024-01-01T00:00:00Z" } ] ``` > **Info: Filtering Tips** > You can combine multiple filters (id, externalId, tenantId, partnerId) in a single request. When multiple filters are provided, results match any of the specified filters. Use pagination (take/skip) to efficiently browse large result sets. --- ### (POST) Create links Create one or more links in a single request. Each link requires a destination URL and collection ID. Canonical URL: https://hoko.to/docs/links/post Markdown URL: https://hoko.to/docs/links/post/README.md ## 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, 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 HTTP/HTTPS URL. | | collectionId | string (UUID) | Yes | body | The ID of the collection (folder) to organize this link. The collection must 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 HTTP/HTTPS URL. | | utm | object | No | body | UTM parameters for campaign tracking. Supports 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 instead of redirecting. 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 | No | body | Country-code destination map such as `{ "SA": "https://example.com/sa" }`. Requires geo targeting. | | 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. ## Status Codes ### 201 Links created successfully. **Request** ```bash curl -X POST "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "url": "https://example.com", "collectionId": "550e8400-e29b-41d4-a716-446655440000", "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 ', 'Content-Type': 'application/json' }, body: JSON.stringify([ { url: 'https://example.com', collectionId: '550e8400-e29b-41d4-a716-446655440000', 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", "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" }, "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 " \ -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 ', '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 " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'POST', headers: { Authorization: 'Bearer ' } }); ``` **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 " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'POST', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X POST "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "url": "https://example.com", "title": "Example Link", "description": "An example link", "collectionId": "550e8400-e29b-41d4-a716-446655440000", "utm": { "source": "google", "medium": "cpc", "campaign": "summer" } } ]' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify([ { url: 'https://example.com', title: 'Example Link', description: 'An example link', collectionId: '550e8400-e29b-41d4-a716-446655440000', 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", "collectionId": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2024-01-01T00:00:00Z" } ] ``` > **Warning: Important Constraints** > The collectionId must reference an existing collection 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. --- ### (PUT) Update/Upsert links Update existing links or create new ones with upsert operations. Perfect for synchronization scenarios. Canonical URL: https://hoko.to/docs/links/put Markdown URL: https://hoko.to/docs/links/put/README.md ## 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. | Parameter | Type | Required | Location | Description | | ------------ | ------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | id | string (UUID) | Yes | body | Required when updating. Omit to create a new link. If provided, it must exist in your workspace or the request fails. | | url | string (URL) | No | body | Destination URL. Required when creating a new link (no id). Optional when updating. | | collectionId | string (UUID) | No | body | Collection ID. Required when creating a new link (no id). Optional when updating. | | title | string | No | body | Link title. Only changes when provided. | | description | string | No | body | Link description. Only changes when provided. | | image | string (URL) | No | body | Preview image URL. Only changes when provided. | | utm | object | No | body | UTM parameters object. Only the provided UTM keys are updated. | | expiresAt | string/null | No | body | ISO date-time when the link should expire. Send null to clear. Requires link expiration when setting a value. | | expiredUrl | string/null | No | body | Destination used after expiration. Send null to clear. Requires link expiration when setting a value. | | password | string/null | No | body | Password required before resolving the destination. Send null to clear. Requires password protection when set. | | cloaked | boolean/null | No | body | Render the resolved destination in an iframe. Send false/null to clear. Requires link cloaking when true. | | ios | string/null | No | body | Destination for iOS visitors. Send null to clear. Requires device targeting when setting a value. | | android | string/null | No | body | Destination for Android visitors. Send null to clear. Requires device targeting when setting a value. | | geo | object/null | No | body | Country-code destination map. Send null to clear. Requires geo targeting when setting destinations. | | externalId | string | No | body | External ID for syncing. Must be unique in workspace when provided. | | tenantId | string | No | body | Tenant identifier. Optional. | | partnerId | string (UUID) | No | body | Partner ID for attribution. Optional. | > **Warning: 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. ## Status Codes ### 200 Links updated or created successfully. **Request** ```bash curl -X PUT "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://updated.com", "title": "Updated Link", "expiresAt": "2026-12-31T23:59:00Z", "expiredUrl": "https://updated.com/expired", "cloaked": true, "ios": "https://updated.com/ios", "android": "https://updated.com/android", "geo": { "US": "https://updated.com/us" }, "collectionId": "550e8400-e29b-41d4-a716-446655440000" } ]' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'PUT', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify([ { id: '550e8400-e29b-41d4-a716-446655440000', url: 'https://updated.com', title: 'Updated Link', expiresAt: '2026-12-31T23:59:00Z', expiredUrl: 'https://updated.com/expired', cloaked: true, ios: 'https://updated.com/ios', android: 'https://updated.com/android', geo: { US: 'https://updated.com/us' }, collectionId: '550e8400-e29b-41d4-a716-446655440000' } ]) }); 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", "expiresAt": "2026-12-31T23:59:00Z", "expiredUrl": "https://updated.com/expired", "cloaked": true, "ios": "https://updated.com/ios", "android": "https://updated.com/android", "geo": { "US": "https://updated.com/us" }, "createdAt": "2024-01-01T00:00:00Z" } ] ``` ### 400 Invalid request body (missing required fields or invalid values). **Request** ```bash curl -X PUT "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "url": "invalid-url" } ]' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'PUT', headers: { Authorization: 'Bearer ', '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 PUT "https://hoko.to/api/links" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'PUT', 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 PUT "https://hoko.to/api/links" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'PUT', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["linksWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-\* headers for details. **Request** ```bash curl -X PUT "https://hoko.to/api/links" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'PUT', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X PUT "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -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" } ]' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/links', { method: 'PUT', headers: { Authorization: 'Bearer ', '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' } ]) }); 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", "collectionId": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2024-01-01T00:00:00Z" } ] ``` > **Info: Upsert Behavior** > When updating, only the fields you include are changed and omitted fields keep their current values. To clear an optional field, send it explicitly as null. When creating a new link (no id), url and collectionId are required. --- ### (DELETE) Delete links Delete one or more links using soft delete. Historical analytics data is preserved. Canonical URL: https://hoko.to/docs/links/delete Markdown URL: https://hoko.to/docs/links/delete/README.md ## Endpoint DELETE /api/links Delete one or more links using soft delete. Soft delete marks links as deleted without permanently removing them from the database, allowing for potential recovery and maintaining referential integrity. Accepts an array of link IDs to delete. All specified links must belong to your workspace. The endpoint returns the IDs of successfully deleted links. Deleted links are no longer accessible via the API and will not appear in GET requests, but historical analytics data associated with these links is preserved. **Endpoint** ```text DELETE /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 IDs (UUIDs) to delete. You can delete 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 | |---|---|---|---|---| | body | array | Yes | body | Array of link IDs (UUIDs) to delete. All IDs must be valid and belong to your workspace. | > **Warning: Request Size** > You can delete up to 10,000 links in a single request. Requests above 10,000 IDs are rejected. ## Status Codes ### 200 Links deleted successfully. **Request** ```bash curl -X DELETE "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/links", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); const result = await response.json(); ``` **Response** ```json { "deletedIds": [ "550e8400-e29b-41d4-a716-446655440000" ] } ``` ### 400 Invalid request body (missing IDs, invalid UUIDs, or IDs not found in your workspace). **Request** ```bash curl -X DELETE "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "invalid-id" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/links", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "invalid-id" ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X DELETE "https://hoko.to/api/links" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/links", { method: "DELETE", 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 DELETE "https://hoko.to/api/links" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/links", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["linksWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X DELETE "https://hoko.to/api/links" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/links", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X DELETE "https://hoko.to/api/links" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/links", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); const result = await response.json(); ``` **Response** ```json { "deletedIds": [ "550e8400-e29b-41d4-a716-446655440000" ] } ``` > **Warning: Permanent Action** > Deleted links cannot be restored through the API. While the data is preserved for analytics purposes, the links themselves are no longer accessible. Ensure you have backups or exports before performing bulk deletions. --- ### Introduction Organize your links with collections. Collections are essential organizational units that help you structure and manage your link library. Canonical URL: https://hoko.to/docs/collections/introduction Markdown URL: https://hoko.to/docs/collections/introduction/README.md ## Overview Collections, provide a powerful way to organize and categorize your links. Think of collections as containers that help you group related links together—whether by campaign, project, client, or any other organizational structure that makes sense for your workflow. Every link must belong to a collection, making collections a fundamental organizational unit in Hoko. Use collections to create logical groupings that make it easier to find, manage, and analyze your links. The Collections API provides full CRUD operations for managing your organizational structure. Create collections before creating links, and use them to maintain a clean, organized link library. - (GET) Retrieve collections with pagination and filtering - (POST) Create one or more collections to organize your links - (PUT) Update existing collections or create new ones with upsert - (DELETE) Soft delete collections while preserving link relationships > **Tip: Organization Strategy** > Create collections before creating links. Plan your collection structure to match your workflow—consider organizing by campaign, product line, marketing channel, or client. You can always reorganize later, but a good initial structure saves time. --- ### (GET) Get collections Retrieve collections from your workspace. Filter by collection ID or list all collections with pagination. Canonical URL: https://hoko.to/docs/collections/get Markdown URL: https://hoko.to/docs/collections/get/README.md ## Endpoint GET /api/collections Retrieve collections from your workspace. Filter by collection ID to get specific collections, or omit the filter to retrieve all collections with pagination support. Use this endpoint to list your collections, verify collection existence before creating links, or build collection management interfaces. **Endpoint** ```text GET /api/collections ``` ## Authentication Requires authentication with an API key that has the collectionsRead scope. ## Query Parameters | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | id | string (UUID) | No | query | Filter by specific collection ID. Returns a single collection if found. | | take | number | No | query | Number of results to return (default: 50, min: 1, max: 10,000). | | skip | number | No | query | Number of results to skip for pagination (default: 0, min: 0). | | sort | string | No | query | Sort order by createdAt: "asc" for oldest first, "desc" for newest first (default: "desc"). | > **Warning: Request Limits** > The `take` parameter supports values from 1 to 10,000. Requests above 10,000 are rejected. ## Status Codes ### 200 Successfully retrieved collections. **Request** ```bash curl -X GET "https://hoko.to/api/collections?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing Campaigns", "description": "All marketing links", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X GET "https://hoko.to/api/collections" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "GET", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required collectionsRead scope. **Request** ```bash curl -X GET "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["collectionsRead"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X GET "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X GET "https://hoko.to/api/collections?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); const collections = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing Campaigns", "description": "All marketing links", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ### (POST) Create collections Create one or more collections to organize your links. Each collection requires a name. Canonical URL: https://hoko.to/docs/collections/post Markdown URL: https://hoko.to/docs/collections/post/README.md ## Endpoint POST /api/collections Create one or more collections to organize your links. Collections are essential organizational units—every link must belong to a collection. Each collection requires a name for identification. Optionally include a description to provide additional context about the collection's purpose or contents. You can create multiple collections in a single request by providing an array of collection objects, making it easy to set up your organizational structure quickly. **Endpoint** ```text POST /api/collections ``` ## Authentication Requires authentication with an API key that has the collectionsWrite scope. ## Request Body The request body must be an array of collection objects. Each object represents one collection to create. You can create up to 10,000 collections 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 | |---|---|---|---|---| | name | string | Yes | body | The name of the collection. Used for identification and organization. Choose descriptive names that clearly indicate the collection's purpose. | | description | string | No | body | Optional description providing additional context about the collection. Useful for documentation and team collaboration. | > **Warning: Plan Limits** > The actual maximum number of collections 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. ## Status Codes ### 201 Collections created successfully. **Request** ```bash curl -X POST "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Marketing Campaigns" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Marketing Campaigns" } ]) }); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing Campaigns", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` ### 400 Invalid request body (missing required fields or invalid values). **Request** ```bash curl -X POST "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ {} ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ {} ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X POST "https://hoko.to/api/collections" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "POST", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required collectionsWrite scope. **Request** ```bash curl -X POST "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "POST", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["collectionsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X POST "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "POST", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X POST "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Marketing Campaigns", "description": "All marketing campaign links" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Marketing Campaigns", description: "All marketing campaign links" } ]) }); const collections = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing Campaigns", "description": "All marketing campaign links", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` > **Info: Collection Requirements** > Every link must belong to a collection. Create collections before creating links, or ensure the collectionId you reference in link creation requests exists in your workspace. --- ### (PUT) Update/Upsert collections Update existing collections or create new ones with upsert operations. Canonical URL: https://hoko.to/docs/collections/put Markdown URL: https://hoko.to/docs/collections/put/README.md ## Endpoint PUT /api/collections Upsert (update or insert) one or more collections. If id is provided, the collection is updated. If the ID does not exist, the request fails. To create new collections, omit the id field. This endpoint is useful for synchronization scenarios where you want to ensure collections exist with specific properties. **Endpoint** ```text PUT /api/collections ``` ## Authentication Requires authentication with an API key that has the collectionsWrite scope. ## Request Body The request body must be an array of collection objects. Include the id field to update an existing collection, or omit it to create a new one. You can upsert up to 10,000 collections 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 | |---|---|---|---|---| | id | string (UUID) | Yes | body | Required when updating. Omit to create a new collection. If provided, it must exist in your workspace or the request fails. | | name | string | No | body | The name of the collection. Required when creating a new collection (no id). Optional when updating. | | description | string | No | body | Optional description. Only changes when provided. | > **Info: Update Behavior** > When updating, only the fields you include are changed. Omitted fields keep their current values. To clear a value, send it explicitly as null. > **Warning: Plan Limits** > The actual maximum number of collections 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. ## Status Codes ### 200 Collections updated or created successfully. **Request** ```bash curl -X PUT "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Name" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { id: "550e8400-e29b-41d4-a716-446655440000", name: "Updated Name" } ]) }); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Name", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` ### 400 Invalid request body (missing required fields or invalid values). **Request** ```bash curl -X PUT "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ {} ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ {} ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X PUT "https://hoko.to/api/collections" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "PUT", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required collectionsWrite scope. **Request** ```bash curl -X PUT "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "PUT", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["collectionsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X PUT "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "PUT", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X PUT "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Name", "description": "Updated description" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { id: "550e8400-e29b-41d4-a716-446655440000", name: "Updated Name", description: "Updated description" } ]) }); const collections = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Name", "description": "Updated description", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ### (DELETE) Delete collections Delete one or more collections using soft delete. Canonical URL: https://hoko.to/docs/collections/delete Markdown URL: https://hoko.to/docs/collections/delete/README.md ## Endpoint DELETE /api/collections Delete one or more collections using soft delete. Soft delete marks collections as deleted without permanently removing them from the database. Accepts an array of collection IDs to delete. All specified collections must belong to your workspace. **Endpoint** ```text DELETE /api/collections ``` ## Authentication Requires authentication with an API key that has the collectionsWrite scope. ## Request Body The request body must be an array of collection IDs (UUIDs) to delete. You can delete up to 10,000 collections in a single request. Requests above 10,000 IDs are rejected. | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | body | array | Yes | body | Array of collection IDs (UUIDs) to delete. All IDs must be valid and belong to your workspace. | > **Warning: Request Size** > Ensure the request body contains at least one valid collection ID. ## Status Codes ### 200 Collections deleted successfully. **Request** ```bash curl -X DELETE "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); ``` **Response** ```json { "deletedIds": ["550e8400-e29b-41d4-a716-446655440000"] } ``` ### 400 Invalid request body (missing IDs, invalid UUIDs, or IDs not found in your workspace). **Request** ```bash curl -X DELETE "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "invalid-id" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "invalid-id" ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X DELETE "https://hoko.to/api/collections" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "DELETE", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required collectionsWrite scope. **Request** ```bash curl -X DELETE "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["collectionsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X DELETE "https://hoko.to/api/collections" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X DELETE "https://hoko.to/api/collections" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/collections", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); const result = await response.json(); ``` **Response** ```json { "deletedIds": ["550e8400-e29b-41d4-a716-446655440000"] } ``` > **Warning: Important Note** > Deleted collections cannot be restored through the API. Links that belong to deleted collections will still exist but may need to be moved to active collections for proper organization. --- ### Introduction Create, read, update, and delete tags for categorizing and organizing your links. Canonical URL: https://hoko.to/docs/tags/introduction Markdown URL: https://hoko.to/docs/tags/introduction/README.md ## Overview Tags help categorize and filter your links, providing a flexible labeling system that complements collections. While collections organize links into folders, tags allow you to apply multiple labels to a single link, enabling cross-cutting categorization. Use tags to mark links by campaign type, content category, target audience, priority level, or any other dimension that helps you organize and find your links. A single link can have multiple tags, making it easy to filter and search across different organizational axes. The Tags API provides full CRUD operations for managing your tag library. Create tags with custom names and colors, update them as your needs evolve, and delete tags that are no longer needed. - (GET) Retrieve tags with pagination and filtering - (POST) Create one or more tags with custom names and colors - (PUT) Update existing tags or create new ones with upsert - (DELETE) Permanently delete tags (hard delete) > **Tip: Tag Strategy** > Plan your tag structure to complement your collection organization. Use tags for cross-cutting concerns that span multiple collections, such as campaign types, content categories, or priority levels. Choose descriptive tag names and use colors to visually distinguish different tag categories. --- ### (GET) Get tags Retrieve tags from your workspace. Filter by tag ID or list all tags with pagination. Canonical URL: https://hoko.to/docs/tags/get Markdown URL: https://hoko.to/docs/tags/get/README.md ## Endpoint GET /api/tags Retrieve tags by id. Supports pagination with take, skip, and sorting by tag id. Use this endpoint to list your tags, verify tag existence, or build tag management interfaces. **Endpoint** ```text GET /api/tags ``` ## Authentication Requires authentication with an API key that has the tagsRead scope. ## Query Parameters | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | id | string (UUID) | No | query | Filter by specific tag ID. Returns a single tag if found. | | take | number | No | query | Number of results to return (default: 50, min: 1, max: 10,000). | | skip | number | No | query | Number of results to skip for pagination (default: 0, min: 0). | | sort | string | No | query | Sort order by tag id: "asc" for oldest first, "desc" for newest first (default: "desc"). | > **Warning: Request Limits** > The `take` parameter supports values from 1 to 10,000. Requests above 10,000 are rejected. ## Status Codes ### 200 Successfully retrieved tags. **Request** ```bash curl -X GET "https://hoko.to/api/tags?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); const tags = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing", "color": "blue" } ] ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X GET "https://hoko.to/api/tags" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "GET", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required tagsRead scope. **Request** ```bash curl -X GET "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["tagsRead"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X GET "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X GET "https://hoko.to/api/tags?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); const tags = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing", "color": "blue" } ] ``` --- ### (POST) Create tags Create one or more tags. Each tag requires a name, and color is optional. Canonical URL: https://hoko.to/docs/tags/post Markdown URL: https://hoko.to/docs/tags/post/README.md ## Endpoint POST /api/tags Create one or more tags. Accepts an array of tag objects. Each tag requires a name. Color is optional (defaults to "slate"). **Endpoint** ```text POST /api/tags ``` ## Authentication Requires authentication with an API key that has the tagsWrite scope. ## Request Body The request body must be an array of tag objects. Each object represents one tag to create. You can create up to 10,000 tags 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 | |---|---|---|---|---| | name | string | Yes | body | The name of the tag. Choose descriptive names that clearly indicate the tag's purpose. | | color | string | No | body | The color of the tag. Must be one of the available color values (default: "slate"). | > **Warning: Plan Limits** > The actual maximum number of tags 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. ## Status Codes ### 201 Tags created successfully. **Request** ```bash curl -X POST "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Marketing", "color": "blue" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Marketing", color: "blue" } ]) }); const tags = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing", "color": "blue" } ] ``` ### 400 Invalid request body (missing required fields or invalid values). **Request** ```bash curl -X POST "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Marketing", "color": "invalid-color" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Marketing", color: "invalid-color" } ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X POST "https://hoko.to/api/tags" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "POST", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required tagsWrite scope. **Request** ```bash curl -X POST "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "POST", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["tagsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X POST "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "POST", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples ### Available Colors Type: color - slate - gray - zinc - neutral - stone - red - orange - amber - yellow - lime - green - emerald - teal - cyan - sky - blue - indigo - violet - purple - fuchsia - pink - rose **Request** ```bash curl -X POST "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Marketing", "color": "blue" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Marketing", color: "blue" } ]) }); const tags = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Marketing", "color": "blue" } ] ``` --- ### (PUT) Update/Upsert tags Update existing tags or create new ones with upsert operations. Canonical URL: https://hoko.to/docs/tags/put Markdown URL: https://hoko.to/docs/tags/put/README.md ## Endpoint PUT /api/tags Upsert (update or insert) one or more tags. If id is provided, the tag is updated. If the ID does not exist, the request fails. To create new tags, omit the id field. This endpoint is useful for synchronization scenarios where you want to ensure tags exist with specific properties. **Endpoint** ```text PUT /api/tags ``` ## Authentication Requires authentication with an API key that has the tagsWrite scope. ## Request Body The request body must be an array of tag objects. Include the id field to update an existing tag, or omit it to create a new one. You can upsert up to 10,000 tags 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 | |---|---|---|---|---| | id | string (UUID) | Yes | body | Required when updating. Omit to create a new tag. If provided, it must exist in your workspace or the request fails. | | name | string | No | body | The name of the tag. Required when creating a new tag (no id). Optional when updating. | | color | string | No | body | The color of the tag. Optional. Defaults to \"slate\" when creating a new tag. | > **Info: Update Behavior** > When updating, only the fields you include are changed. Omitted fields keep their current values. For new tags (no id), color defaults to \"slate\" if omitted. > **Warning: Plan Limits** > The actual maximum number of tags 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. ## Status Codes ### 200 Tags updated or created successfully. **Request** ```bash curl -X PUT "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Tag", "color": "green" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { id: "550e8400-e29b-41d4-a716-446655440000", name: "Updated Tag", color: "green" } ]) }); const tags = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Tag", "color": "green" } ] ``` ### 400 Invalid request body (missing required fields or invalid values). **Request** ```bash curl -X PUT "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Tag", "color": "invalid-color" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Tag", color: "invalid-color" } ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X PUT "https://hoko.to/api/tags" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "PUT", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required tagsWrite scope. **Request** ```bash curl -X PUT "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "PUT", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["tagsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X PUT "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "PUT", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples ### Available Colors Type: color - slate - gray - zinc - neutral - stone - red - orange - amber - yellow - lime - green - emerald - teal - cyan - sky - blue - indigo - violet - purple - fuchsia - pink - rose **Request** ```bash curl -X PUT "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Tag", "color": "green" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { id: "550e8400-e29b-41d4-a716-446655440000", name: "Updated Tag", color: "green" } ]) }); const tags = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Tag", "color": "green" } ] ``` --- ### (DELETE) Delete tags Permanently delete one or more tags (hard delete). Canonical URL: https://hoko.to/docs/tags/delete Markdown URL: https://hoko.to/docs/tags/delete/README.md ## Endpoint DELETE /api/tags Delete one or more tags (hard delete). This permanently removes tags from your workspace. Accepts an array of tag IDs to delete. All specified tags must belong to your workspace. **Endpoint** ```text DELETE /api/tags ``` ## Authentication Requires authentication with an API key that has the tagsWrite scope. ## Request Body The request body must be an array of tag IDs (UUIDs) to delete. You can delete up to 10,000 tags in a single request. Requests above 10,000 IDs are rejected. | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | body | array | Yes | body | Array of tag IDs (UUIDs) to delete. All IDs must be valid and belong to your workspace. | > **Warning: Request Size** > Ensure the request body contains at least one valid tag ID. ## Status Codes ### 200 Tags deleted successfully. **Request** ```bash curl -X DELETE "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); const result = await response.json(); ``` **Response** ```json { "deletedIds": [ "550e8400-e29b-41d4-a716-446655440000" ] } ``` ### 400 Invalid request body (missing IDs, invalid UUIDs, or IDs not found in your workspace). **Request** ```bash curl -X DELETE "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "invalid-id" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "invalid-id" ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X DELETE "https://hoko.to/api/tags" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "DELETE", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required tagsWrite scope. **Request** ```bash curl -X DELETE "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["tagsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X DELETE "https://hoko.to/api/tags" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X DELETE "https://hoko.to/api/tags" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/tags", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); const result = await response.json(); ``` **Response** ```json { "deletedIds": [ "550e8400-e29b-41d4-a716-446655440000" ] } ``` > **Warning: Permanent Action** > Deleted tags cannot be restored. This is a hard delete operation that permanently removes tags from your workspace. Links that had these tags will no longer have those tag associations. --- ### Introduction Retrieve click analytics and performance data for your links. Gain insights into traffic patterns, user behavior, and campaign effectiveness. Canonical URL: https://hoko.to/docs/analytics/introduction Markdown URL: https://hoko.to/docs/analytics/introduction/README.md ## Overview The Analytics API provides read-only access to comprehensive click data and analytics for your links. Track every click, understand your audience, and measure campaign performance with detailed insights. Every click on your short links is automatically tracked and enriched with metadata including device information, geographic location, browser details, referrer data, and UTM parameters. This data is immediately available through the Analytics API for real-time analysis and reporting. Use the Analytics API to build custom dashboards, generate reports, analyze traffic patterns, measure campaign ROI, and understand how users interact with your links across different channels and devices. - (GET) Retrieve click analytics with flexible filtering by link, partner, or date range - Detailed click data including device, location, browser, and UTM parameters - Support for pagination and sorting to efficiently process large datasets - Real-time analytics data available immediately after clicks occur > **Info: Data Availability** > Analytics data is available immediately after a click occurs. Use date range filters to analyze specific time periods, and combine with link or partner filters to drill down into specific segments of your traffic. --- ### (GET) Get analytics Retrieve click analytics and performance data for your links. Filter by link, partner, or date range. Canonical URL: https://hoko.to/docs/analytics/get Markdown URL: https://hoko.to/docs/analytics/get/README.md ## Endpoint GET /api/analytics Retrieve click analytics and performance data for your links. Filter by link, partner, or date range to analyze specific segments of your traffic. Supports flexible filtering and pagination, making it easy to extract insights from large datasets. Results include detailed click information including device, location, browser, and UTM parameters. **Endpoint** ```text GET /api/analytics ``` ## Authentication Requires authentication with an API key that has the analyticsRead scope. ## Query Parameters | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | linkId | string (UUID) | No | query | Filter analytics by specific link ID. Returns click data only for the specified link. | | partnerId | string (UUID) | No | query | Filter analytics by partner ID. Returns click data for all links associated with the specified partner. | | startDate | string (ISO 8601) | No | query | Start date for the date range filter. Must be in ISO 8601 format (e.g., "2024-01-01T00:00:00Z"). Only clicks on or after this date are returned. | | endDate | string (ISO 8601) | No | query | End date for the date range filter. Must be in ISO 8601 format (e.g., "2024-01-31T23:59:59Z"). Only clicks on or before this date are returned. | | take | number | No | query | Number of results to return (default: 50, min: 1, max: 10,000). Use pagination to retrieve large datasets efficiently. | | skip | number | No | query | Number of results to skip for pagination (default: 0, min: 0). Use with take to implement pagination. | | sort | string | No | query | Sort order by createdAt: "asc" for oldest clicks first, "desc" for newest clicks first (default: "desc"). | > **Warning: Request Limits** > The `take` parameter supports values from 1 to 10,000. Requests above 10,000 are rejected. ## Status Codes ### 200 Successfully retrieved analytics data. **Request** ```bash curl -X GET "https://hoko.to/api/analytics?linkId=550e8400-e29b-41d4-a716-446655440000&take=50" \ -H "Authorization: Bearer " ``` **Request** ```javascript const params = new URLSearchParams({ linkId: "550e8400-e29b-41d4-a716-446655440000", take: "50" }); const response = await fetch(`https://hoko.to/api/analytics?${params}`, { method: "GET", headers: { "Authorization": "Bearer " } }); const analytics = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "linkId": "550e8400-e29b-41d4-a716-446655440000", "browserName": "Chrome", "deviceType": "desktop", "country": "United States", "createdAt": "2024-01-01T00:00:00Z" } ] ``` ### 400 Invalid query parameters (e.g., malformed dates or UUIDs). **Request** ```bash curl -X GET "https://hoko.to/api/analytics?startDate=invalid-date" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/analytics?startDate=invalid-date", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X GET "https://hoko.to/api/analytics" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/analytics", { method: "GET", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required analyticsRead scope. **Request** ```bash curl -X GET "https://hoko.to/api/analytics" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/analytics", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["analyticsRead"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X GET "https://hoko.to/api/analytics" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/analytics", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X GET "https://hoko.to/api/analytics?linkId=550e8400-e29b-41d4-a716-446655440000&startDate=2024-01-01T00:00:00Z&endDate=2024-01-31T23:59:59Z&take=50" \ -H "Authorization: Bearer " ``` **Request** ```javascript const params = new URLSearchParams({ linkId: "550e8400-e29b-41d4-a716-446655440000", startDate: "2024-01-01T00:00:00Z", endDate: "2024-01-31T23:59:59Z", take: "50" }); const response = await fetch(`https://hoko.to/api/analytics?${params}`, { method: "GET", headers: { "Authorization": "Bearer " } }); const analytics = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "linkId": "550e8400-e29b-41d4-a716-446655440000", "partnerId": "550e8400-e29b-41d4-a716-446655440000", "visitorId": "visitor_123", "visitorSessionId": "session_123", "referrer": "https://google.com", "destination": "https://example.com", "shortId": "abc123", "utm": { "source": "google", "medium": "cpc", "campaign": "summer" }, "externalId": "ext_123", "tenantId": "tenant_123", "browserName": "Chrome", "browserVersion": "120.0", "osName": "Windows", "osVersion": "10", "deviceType": "desktop", "country": "United States", "countryCode": "US", "city": "New York", "createdAt": "2024-01-01T00:00:00Z" } ] ``` --- ### Introduction Read customer data. Customer creation and updates are handled automatically through conversion tracking endpoints. Canonical URL: https://hoko.to/docs/customers/introduction Markdown URL: https://hoko.to/docs/customers/introduction/README.md ## Overview The Customers API provides read-only access to customer data. Customers are automatically created or updated when you track lead or sale conversion events, making it easy to maintain a synchronized customer database without manual intervention. When you track a lead or sale event using the conversion tracking endpoints (/api/track/lead or /api/track/sale), Hoko automatically creates or updates the customer record based on the customerExternalId. This ensures your customer data stays in sync with your conversion tracking activities. Use the Customers API to retrieve customer information, verify customer existence, build customer management interfaces, or integrate customer data with other systems. The API supports filtering by customer ID or external ID, making it easy to find specific customers. - (GET) Retrieve customers by ID or external ID with pagination support - Automatic customer creation/updates through conversion tracking endpoints - Read-only access—customer write operations handled via conversion tracking - Support for filtering and sorting to efficiently find specific customers > **Info: Customer Management** > Customer write operations (POST, PUT, DELETE) are not available through the Customers API. Customers are automatically created or updated when tracking lead or sale events using the conversionsWrite scope. This design ensures data consistency and simplifies your integration workflow. --- ### (GET) Get customers Retrieve customers by ID or external ID. Supports pagination and sorting. Canonical URL: https://hoko.to/docs/customers/get Markdown URL: https://hoko.to/docs/customers/get/README.md ## Endpoint GET /api/customers Retrieve customers by id or externalId. Supports pagination with take, skip, and sorting by createdAt. Use this endpoint to list your customers, verify customer existence, or build customer management interfaces. **Endpoint** ```text GET /api/customers ``` ## Authentication Requires authentication with an API key that has the customersRead scope. ## Query Parameters | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | id | string (UUID) | No | query | Filter by specific customer ID. Returns a single customer if found. | | externalId | string | No | query | Filter by external ID. Useful for syncing with external systems. | | take | number | No | query | Number of results to return (default: 50, min: 1, max: 10,000). | | skip | number | No | query | Number of results to skip for pagination (default: 0, min: 0). | | sort | string | No | query | Sort order by createdAt: "asc" for oldest first, "desc" for newest first (default: "desc"). | > **Warning: Request Limits** > The `take` parameter supports values from 1 to 10,000. Requests above 10,000 are rejected. ## Status Codes ### 200 Successfully retrieved customers. **Request** ```bash curl -X GET "https://hoko.to/api/customers?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/customers?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); const customers = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "externalId": "customer_123", "name": "John Doe", "email": "john@example.com", "phone": "+1234567890", "avatar": "https://example.com/avatar.jpg", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X GET "https://hoko.to/api/customers" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/customers", { method: "GET", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required customersRead scope. **Request** ```bash curl -X GET "https://hoko.to/api/customers" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/customers", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["customersRead"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X GET "https://hoko.to/api/customers" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/customers", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X GET "https://hoko.to/api/customers?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/customers?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); const customers = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "externalId": "customer_123", "name": "John Doe", "email": "john@example.com", "phone": "+1234567890", "avatar": "https://example.com/avatar.jpg", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ### Introduction Create, read, update, and delete partners for attribution tracking and commission management. Canonical URL: https://hoko.to/docs/partners/introduction Markdown URL: https://hoko.to/docs/partners/introduction/README.md ## Overview Partners help track attribution and commission data for affiliate programs, referral systems, and partner marketing campaigns. Associate partners with links to automatically attribute clicks and conversions to the correct partner. Use partners to track which affiliates or referral sources are driving traffic and conversions, calculate commissions, and measure partner performance. Each partner can be associated with multiple links, enabling comprehensive attribution tracking across your marketing channels. The Partners API provides full CRUD operations for managing your partner database. Create partners, update their information, and track their performance through associated links and conversions. - (GET) Retrieve partners with pagination and filtering - (POST) Create one or more partners with contact information - (PUT) Update existing partners or create new ones with upsert - (DELETE) Soft delete partners while preserving attribution data > **Tip: Partner Attribution** > Associate partners with links using the partnerId field when creating or updating links. This enables automatic attribution of clicks and conversions to the correct partner, making it easy to track partner performance and calculate commissions. --- ### (GET) Get partners Retrieve partners by ID. Supports pagination and sorting. Canonical URL: https://hoko.to/docs/partners/get Markdown URL: https://hoko.to/docs/partners/get/README.md ## Endpoint GET /api/partners Retrieve partners by id. Supports pagination with take, skip, and sorting by createdAt. **Endpoint** ```text GET /api/partners ``` ## Authentication Requires authentication with an API key that has the partnersRead scope. ## Query Parameters | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | id | string (UUID) | No | query | Filter by specific partner ID. Returns a single partner if found. | | take | number | No | query | Number of results to return (default: 50, min: 1, max: 10,000). | | skip | number | No | query | Number of results to skip for pagination (default: 0, min: 0). | | sort | string | No | query | Sort order by createdAt: "asc" for oldest first, "desc" for newest first (default: "desc"). | > **Warning: Request Limits** > The `take` parameter supports values from 1 to 10,000. Requests above 10,000 are rejected. ## Status Codes ### 200 Successfully retrieved partners. **Request** ```bash curl -X GET "https://hoko.to/api/partners?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); const partners = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Partner Name", "email": "partner@example.com", "phone": "+1234567890", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X GET "https://hoko.to/api/partners" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "GET", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required partnersRead scope. **Request** ```bash curl -X GET "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["partnersRead"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X GET "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "GET", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X GET "https://hoko.to/api/partners?take=50&sort=desc" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners?take=50&sort=desc", { method: "GET", headers: { "Authorization": "Bearer " } }); const partners = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Partner Name", "email": "partner@example.com", "phone": "+1234567890", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ### (POST) Create partners Create one or more partners. Each partner requires a name. Canonical URL: https://hoko.to/docs/partners/post Markdown URL: https://hoko.to/docs/partners/post/README.md ## Endpoint POST /api/partners Create one or more partners. Accepts an array of partner objects. Each partner requires a name. Email and phone are optional. **Endpoint** ```text POST /api/partners ``` ## Authentication Requires authentication with an API key that has the partnersWrite scope. ## Request Body The request body must be an array of partner objects. Each object represents one partner to create. You can create up to 10,000 partners in a single request. Requests above 10,000 items are rejected. | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | name | string | Yes | body | The name of the partner. | | email | string (email) | No | body | Email address of the partner. | | phone | string | No | body | Phone number of the partner. | > **Warning: Request Size** > Ensure the request body contains at least one partner object. ## Status Codes ### 201 Partners created successfully. **Request** ```bash curl -X POST "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Partner Name", "email": "partner@example.com", "phone": "+1234567890" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Partner Name", email: "partner@example.com", phone: "+1234567890" } ]) }); const partners = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Partner Name", "email": "partner@example.com", "phone": "+1234567890", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` ### 400 Invalid request body (missing required fields or invalid values). **Request** ```bash curl -X POST "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ {} ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ {} ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X POST "https://hoko.to/api/partners" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "POST", headers: { "Authorization": "Bearer invalid_key" } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required partnersWrite scope. **Request** ```bash curl -X POST "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "POST", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["partnersWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X POST "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "POST", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X POST "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "name": "Partner Name", "email": "partner@example.com", "phone": "+1234567890" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "POST", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { name: "Partner Name", email: "partner@example.com", phone: "+1234567890" } ]) }); const partners = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Partner Name", "email": "partner@example.com", "phone": "+1234567890", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ### (PUT) Update/Upsert partners Update existing partners or create new ones with upsert operations. Canonical URL: https://hoko.to/docs/partners/put Markdown URL: https://hoko.to/docs/partners/put/README.md ## Endpoint PUT /api/partners Upsert (update or insert) one or more partners. If id is provided, the partner is updated. If the ID does not exist, the request fails. To create new partners, omit the id field. **Endpoint** ```text PUT /api/partners ``` ## Authentication Requires authentication with an API key that has the linksWrite scope. ## Request Body The request body must be an array of partner objects. Include the id field to update an existing partner, or omit it to create a new one. You can upsert up to 10,000 partners in a single request. Requests above 10,000 items are rejected. | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | id | string (UUID) | Yes | body | Required when updating. Omit to create a new partner. If provided, it must exist in your workspace or the request fails. | | name | string | No | body | The name of the partner. Required when creating a new partner (no id). Optional when updating. | | email | string (email) | No | body | Email address of the partner. Optional for both create and update operations. | | phone | string | No | body | Phone number of the partner. Optional for both create and update operations. | > **Info: Update Behavior** > When updating, only the fields you include are changed. Omitted fields keep their current values. To clear a value, send it explicitly as null. > **Warning: Request Size** > Ensure the request body contains at least one partner object. ## Status Codes ### 200 Partners updated or created successfully. **Request** ```bash curl -X PUT "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Partner", "email": "updated@example.com" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { id: "550e8400-e29b-41d4-a716-446655440000", name: "Updated Partner", email: "updated@example.com" } ]) }); const partners = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Partner", "email": "updated@example.com", "phone": null, "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` ### 400 Invalid request body (missing required fields or invalid values). **Request** ```bash curl -X PUT "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ {} ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ {} ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X PUT "https://hoko.to/api/partners" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "PUT", 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 PUT "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "PUT", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["linksWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X PUT "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "PUT", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X PUT "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Partner", "email": "updated@example.com" } ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "PUT", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ { id: "550e8400-e29b-41d4-a716-446655440000", name: "Updated Partner", email: "updated@example.com" } ]) }); const partners = await response.json(); ``` **Response** ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Updated Partner", "email": "updated@example.com", "phone": null, "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ] ``` --- ### (DELETE) Delete partners Delete one or more partners using soft delete. Canonical URL: https://hoko.to/docs/partners/delete Markdown URL: https://hoko.to/docs/partners/delete/README.md ## Endpoint DELETE /api/partners Delete one or more partners (soft delete). Accepts an array of partner IDs to delete. **Endpoint** ```text DELETE /api/partners ``` ## Authentication Requires authentication with an API key that has the linksWrite scope. ## Request Body The request body must be an array of partner IDs (UUIDs) to delete. You can delete up to 10,000 partners in a single request. Requests above 10,000 IDs are rejected. | Parameter | Type | Required | Location | Description | |---|---|---|---|---| | body | array | Yes | body | Array of partner IDs (UUIDs) to delete. All IDs must be valid and belong to your workspace. | > **Warning: Request Size** > Ensure the request body contains at least one valid partner ID. ## Status Codes ### 200 Partners deleted successfully. **Request** ```bash curl -X DELETE "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); const result = await response.json(); ``` **Response** ```json { "deletedIds": [ "550e8400-e29b-41d4-a716-446655440000" ] } ``` ### 400 Invalid request body (missing IDs, invalid UUIDs, or IDs not found in your workspace). **Request** ```bash curl -X DELETE "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "invalid-id" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "invalid-id" ]) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X DELETE "https://hoko.to/api/partners" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "DELETE", 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 DELETE "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["linksWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-* headers for details. **Request** ```bash curl -X DELETE "https://hoko.to/api/partners" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "DELETE", headers: { "Authorization": "Bearer " } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` ## Examples **Request** ```bash curl -X DELETE "https://hoko.to/api/partners" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '[ "550e8400-e29b-41d4-a716-446655440000" ]' ``` **Request** ```javascript const response = await fetch("https://hoko.to/api/partners", { method: "DELETE", headers: { "Authorization": "Bearer ", "Content-Type": "application/json" }, body: JSON.stringify([ "550e8400-e29b-41d4-a716-446655440000" ]) }); const result = await response.json(); ``` **Response** ```json { "deletedIds": [ "550e8400-e29b-41d4-a716-446655440000" ] } ``` --- ### Introduction Track lead and sale conversion events to connect marketing touchpoints to business outcomes. Automatically create and update customer records with flexible attribution tracking. Canonical URL: https://hoko.to/docs/track/introduction Markdown URL: https://hoko.to/docs/track/introduction/README.md ## Overview Conversion tracking is the bridge between your marketing efforts and business outcomes. Track when leads convert and sales happen, connecting every click to revenue and business value. Hoko provides embedded click tracking for owned pages, plus two conversion tracking endpoints: Track Lead for capturing lead events (form submissions, sign-ups, inquiries) and Track Sale for recording completed purchases or revenue-generating actions. Lead and sale endpoints automatically create or update customer records, ensuring your customer database stays synchronized with your conversion tracking activities. When you track a conversion event, Hoko automatically associates it with the click that led to the conversion, providing complete attribution from initial click to final conversion. This enables you to measure campaign ROI, understand customer journeys, and optimize your marketing spend based on actual business results. - Embedded click tracking - Record a click from an owned page without redirecting the visitor - (POST) Track Lead - Record lead conversion events and automatically create/update customer records - (POST) Track Sale - Record sale conversion events with revenue attribution and customer upsert - Automatic customer creation/updates based on customerExternalId - Flexible attribution linking conversions to clicks, links, and partners - Support for immediate and deferred tracking scenarios > **Tip: Getting Started** > Enable conversion tracking on your links, then send lead or sale events when conversions occur. Use customerExternalId to maintain references to customers in your own system, ensuring seamless synchronization between Hoko and your CRM or other systems. --- ### Browser attribution script (analytics.js) Persist the clickId from short-link redirects using analytics.js and send it to conversion endpoints. Canonical URL: https://hoko.to/docs/track/analytics-js Markdown URL: https://hoko.to/docs/track/analytics-js/README.md ## Overview The browser attribution script (`/analytics.js`) captures the `hoko_id` query parameter that Hoko appends to destination URLs and stores it as a cookie. This gives you a persistent clickId that you can send later when tracking leads or sales. ## What it does - Reads `hoko_id` from the current page URL - Stores it in a cookie named `hoko_id` for 90 days - Replaces the existing `hoko_id` cookie when the current page URL contains a new `hoko_id` - Does not send any network requests > **Info: Lightweight by design** > The script only writes the cookie. It does not call any tracking endpoints automatically. ## Add the script Include the script on pages where users land after clicking a Hoko short link. ```html ``` ## Read the cookie and send events Read the `hoko_id` cookie and pass it as `clickId` to conversion tracking endpoints. ```javascript const clickId = document.cookie .split('; ') .find((row) => row.startsWith('hoko_id=')) ?.split('=')[1]; await fetch('https://hoko.to/api/track/lead', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ clickId: clickId ?? '', eventName: 'Sign up', customerExternalId: 'customer_123', customerName: 'John Doe' }) }); ``` ## When it runs The script runs when the browser loads it. It must execute on the same domain where you want the cookie to be stored. --- ### Embedded click tracking Track a Hoko short-link click from an owned page without redirecting the visitor. Canonical URL: https://hoko.to/docs/track/embedded-click Markdown URL: https://hoko.to/docs/track/embedded-click/README.md ## Overview Embedded click tracking lets you record a normal Hoko click when a visitor lands on a page you control, without sending that visitor through the short-link redirect first. Use this when you already know which short link, referral code, partner code, or campaign should receive attribution on the page. ## How it works - Create a dedicated Hoko short link, such as `https://hoko.to/1234` - Add the embedded analytics script using that short ID - Hoko verifies that the page requesting the script is on the same hostname as the short-link destination - Hoko returns JavaScript with a short-lived signed capture token - The script reads or creates a first-party visitor session ID on the page, persists it for 90 days, then sends a signed capture request back to the same script URL - Hoko records the click only after that signed capture request is verified - The script first stores any `hoko_id` already present on the page URL - If the page URL does not include `hoko_id`, Hoko stores the generated embedded click ID as `hoko_id` - Conversion endpoints can later use `hoko_id` as the `clickId` > **Info: Redirect attribution wins first** > The embedded script includes the same page URL behavior as `/analytics.js`, so a `?hoko_id=...` from a normal short-link redirect takes priority. When there is no `hoko_id` on the page URL, the embedded click ID does not overwrite an existing `hoko_id` cookie. ## Add the script ```html ``` The short ID in the path identifies the Hoko link to track. Query parameters on the script URL are captured with the click. You do not need to include `https://hoko.to/analytics.js` alongside this script. The embedded script also persists `hoko_id` from normal short-link redirects. ## Host verification Embedded click tracking only records a click when the page requesting the script and the signed capture request are hosted on the same hostname as the short-link destination. For example, a short link that points to `https://example.com/pricing` can be embedded on `https://example.com/...` or `https://www.example.com/...`, but not on another website. If the browser does not send a `Referer` or `Origin` header, or if the hostname does not match, Hoko returns a no-op script and does not create a click. This protects clients from unrelated websites embedding their script and inflating their analytics. ## Supported attribution parameters | Parameter | Type | Required | Location | Description | | ------------ | ------ | -------- | -------- | --------------------------------------------------------------- | | shortId | string | Yes | path | The Hoko short ID in `/{shortId}/analytics.js`. | | utm_source | string | No | query | UTM source to store on the click. | | utm_medium | string | No | query | UTM medium to store on the click. | | utm_campaign | string | No | query | UTM campaign to store on the click. | | utm_term | string | No | query | UTM term to store on the click. | | utm_content | string | No | query | UTM content to store on the click. | | ref | string | No | query | Referral value to store on the click. | | referral | string | No | query | Alternative referral parameter. Used when `ref` is not present. | ## What gets captured The signed capture request is captured using the same short-link click pipeline as a redirect click. Hoko records request-level attributes such as timestamp, IP-derived location, visitor session ID, user agent, browser, operating system, device, language, referrer, and the UTM/referral parameters in the script URL. For embedded clicks, Hoko stores the browser's full current page URL as the click destination. The server still validates that this URL's hostname matches the configured short-link destination hostname before recording the click. ## Conversion attribution After the script runs, read the `hoko_id` cookie and pass it to conversion endpoints. ```javascript const clickId = document.cookie .split('; ') .find((row) => row.startsWith('hoko_id=')) ?.split('=')[1]; await fetch('https://hoko.to/api/track/lead', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ clickId: clickId ?? '', eventName: 'Sign up', customerExternalId: 'customer_123', customerName: 'John Doe' }) }); ``` ## Behavior notes - The response is not cached, because each script request creates a short-lived capture token. - The script does not redirect the visitor. - A `hoko_id` on the current page URL is stored the same way as `/analytics.js`, including replacing an older cookie value. - Without a page URL `hoko_id`, the embedded click ID does not overwrite an existing `hoko_id` cookie. - The script does not mark clicks as QR scans. Use the normal short-link redirect for QR tracking. - If JavaScript is blocked, the embedded click is not recorded and the `hoko_id` cookie is not stored. - Browser referrer policies may limit how much of the page URL Hoko receives. The embedded script needs either a `Referer` or `Origin` header for host verification. - Pass page-specific attribution in the script query string when needed. --- ### Track lead events Canonical URL: https://hoko.to/docs/track/lead Markdown URL: https://hoko.to/docs/track/lead/README.md ## Endpoint POST /api/track/lead Track a lead conversion event for a short link. This endpoint creates a lead event and upserts a customer record. **Endpoint** ```text POST /api/track/lead ``` ## Authentication Requires authentication with an API key that has the conversionsWrite scope. ## Request Body Accepts a single object (not an array). All fields are required unless marked optional. | Parameter | Type | Required | Location | Description | | ------------------ | -------------- | -------- | -------- | ------------------------------------------------------------------------------- | | clickId | string | Yes | body | Click ID to attribute the lead. Use an empty string ("") for deferred tracking. | | eventName | string | Yes | body | Lead event name (1-255 characters). | | customerExternalId | string | Yes | body | Customer external ID (1-100 characters). Used to upsert the customer. | | customerName | string | Yes | body | Customer name (max 100 characters). | | customerEmail | string (email) | No | body | Customer email. Optional. Can be null to clear. | | customerAvatar | string (URL) | No | body | Customer avatar URL. Optional. Can be null to clear. | | metadata | object | No | body | Optional metadata object stored with the lead event. | > **Info: Deferred Tracking** > Send clickId as an empty string ("") to attribute the lead to the customer's most recent lead click. This fails if the customer has no previous leads. > **Tip: Capture clickId in the browser** > If you don't already have a clickId, use the Browser attribution script to persist `hoko_id` and pass it to this endpoint. See [Browser attribution script](/docs/track/analytics-js). ## Status Codes ### 201 Lead event tracked successfully. **Request** ```bash curl -X POST "https://hoko.to/api/track/lead" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "clickId": "click_abc123", "eventName": "Sign up", "customerExternalId": "customer_123", "customerName": "John Doe", "customerEmail": "john@example.com" }' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/lead', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ clickId: 'click_abc123', eventName: 'Sign up', customerExternalId: 'customer_123', customerName: 'John Doe', customerEmail: 'john@example.com' }) }); const result = await response.json(); ``` **Response** ```json { "click": { "id": "click_abc123" }, "link": { "id": "link_xyz", "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, "collectionId": "550e8400-e29b-41d4-a716-446655440000", "partnerId": "partner_123", "tenantId": "tenant_456", "externalId": "ext_789", "createdAt": "2024-01-01T00:00:00Z" }, "lead": { "eventName": "Sign up", "metadata": { "source": "website" } }, "customer": { "id": "customer_123", "name": "John Doe", "email": "john@example.com", "phone": null, "avatar": "https://example.com/avatar.jpg", "externalId": "customer_123" } } ``` ### 400 Invalid request body, missing required fields, or invalid clickId/deferred tracking. **Request** ```bash curl -X POST "https://hoko.to/api/track/lead" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "clickId": "invalid-click-id", "eventName": "Sign up", "customerExternalId": "customer_123", "customerName": "John Doe" }' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/lead', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ clickId: 'invalid-click-id', eventName: 'Sign up', customerExternalId: 'customer_123', customerName: 'John Doe' }) }); ``` **Response** ```json { "error": { "en": "Click ID invalid-click-id not found or does not belong to your workspace", "ar": "معرف النقرة invalid-click-id غير موجود أو لا ينتمي إلى مساحة العمل الخاصة بك" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X POST "https://hoko.to/api/track/lead" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/lead', { method: 'POST', headers: { Authorization: 'Bearer invalid_key' } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required conversionsWrite scope. **Request** ```bash curl -X POST "https://hoko.to/api/track/lead" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/lead', { method: 'POST', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["conversionsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-\* headers for details. **Request** ```bash curl -X POST "https://hoko.to/api/track/lead" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/lead', { method: 'POST', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` --- ### Track sale events Canonical URL: https://hoko.to/docs/track/sale Markdown URL: https://hoko.to/docs/track/sale/README.md ## Endpoint POST /api/track/sale Track a sale conversion event for a short link. This endpoint creates a sale event and upserts a customer record. **Endpoint** ```text POST /api/track/sale ``` ## Authentication Requires authentication with an API key that has the conversionsWrite scope. ## Request Body Accepts a single object (not an array). All fields are required unless marked optional. | Parameter | Type | Required | Location | Description | | ------------------ | -------------- | -------- | -------- | --------------------------------------------------------------------- | | customerExternalId | string | Yes | body | Customer external ID (1-100 characters). Used to upsert the customer. | | amount | integer | Yes | body | Sale amount (non-negative integer). | | currency | string | No | body | Currency code. Defaults to "usd". | | eventName | string | No | body | Sale event name. Defaults to "Purchase" (max 255 characters). | | metadata | object | No | body | Optional metadata object stored with the sale event. | | leadEventName | string | No | body | Lead event name to attribute this sale to a specific lead. | | clickId | string | No | body | Click ID for direct sale attribution. | | customerName | string | Yes | body | Customer name (max 100 characters). | | customerEmail | string (email) | No | body | Customer email. Optional. Can be null to clear. | | customerAvatar | string (URL) | No | body | Customer avatar URL. Optional. Can be null to clear. | > **Tip: Capture clickId in the browser** > If you want to attribute a sale directly to a click, use the Browser attribution script to persist `hoko_id` and pass it as clickId. See [Browser attribution script](/docs/track/analytics-js). > **Info: Attribution Priority** > The sale is attributed in this order: leadEventName (if provided) → clickId (if provided) → most recent lead for the customer. If no lead exists and no clickId is provided, the request fails. ## Status Codes ### 201 Sale event tracked successfully. **Request** ```bash curl -X POST "https://hoko.to/api/track/sale" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "customerExternalId": "customer_123", "amount": 12900, "currency": "usd", "eventName": "Invoice paid", "customerName": "John Doe", "customerEmail": "john@example.com" }' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/sale', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ customerExternalId: 'customer_123', amount: 12900, currency: 'usd', eventName: 'Invoice paid', customerName: 'John Doe', customerEmail: 'john@example.com' }) }); const result = await response.json(); ``` **Response** ```json { "click": { "id": "click_abc123" }, "link": { "id": "link_xyz", "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, "collectionId": "550e8400-e29b-41d4-a716-446655440000", "partnerId": "partner_123", "tenantId": "tenant_456", "externalId": "ext_789", "createdAt": "2024-01-01T00:00:00Z" }, "customer": { "id": "customer_123", "name": "John Doe", "email": "john@example.com", "phone": null, "avatar": null, "externalId": "customer_123" }, "sale": { "amount": 12900, "currency": "usd", "eventName": "Invoice paid", "metadata": { "invoiceId": "inv_123" } } } ``` ### 400 Invalid request body, negative amount, or missing attribution data. **Request** ```bash curl -X POST "https://hoko.to/api/track/sale" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "customerExternalId": "customer_123", "amount": -100, "customerName": "John Doe" }' ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/sale', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ customerExternalId: 'customer_123', amount: -100, customerName: 'John Doe' }) }); ``` **Response** ```json { "error": { "en": "Invalid request", "ar": "طلب غير صالح" } } ``` ### 401 Invalid or missing API key. **Request** ```bash curl -X POST "https://hoko.to/api/track/sale" \ -H "Authorization: Bearer invalid_key" ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/sale', { method: 'POST', headers: { Authorization: 'Bearer invalid_key' } }); ``` **Response** ```json { "error": { "en": "Invalid API key", "ar": "مفتاح API غير صالح" } } ``` ### 403 API key does not have the required conversionsWrite scope. **Request** ```bash curl -X POST "https://hoko.to/api/track/sale" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/sale', { method: 'POST', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Missing required scopes", "ar": "الصلاحيات المطلوبة مفقودة" }, "missingScopes": ["conversionsWrite"] } ``` ### 429 Rate limit exceeded. Check X-RateLimit-\* headers for details. **Request** ```bash curl -X POST "https://hoko.to/api/track/sale" \ -H "Authorization: Bearer " ``` **Request** ```javascript const response = await fetch('https://hoko.to/api/track/sale', { method: 'POST', headers: { Authorization: 'Bearer ' } }); ``` **Response** ```json { "error": { "en": "Rate limit exceeded", "ar": "تم تجاوز حد المعدل" }, "retryAfter": 60 } ``` --- ## Full Help Documentation ### Getting started Canonical URL: https://hoko.to/help/getting-started # Getting started ## Getting started overview This overview helps you reach your first tracked link quickly. ### What you will set up - Sign in to your account. - Use the default workspace or create a new one. - Create a collection for your first campaign. - Create a link and share it. - Open Analytics to confirm clicks. ### Recommended order 1. Review your profile name and avatar. 2. Rename the workspace if needed. 3. Create a collection for the project. 4. Create a link with a destination. 5. Check Analytics after sharing. > [!INFO] Access is collection-based. If you cannot see a collection, ask an owner for access. ## Create an account Create an account to access your dashboard and workspaces. ### Where to find it - Homepage → Sign up ### Steps 1. Open the sign up page. 2. Enter your email and password. 3. Complete email verification if prompted. 4. Sign in to reach the dashboard. ### Notes - Use a real email address that you can access. - Your first workspace is created automatically on the first sign-in. > [!INFO] If you already have an account, use Sign in. ## Sign in Sign in to access your workspaces and collections. ### Where to find it - Homepage → Sign in ### Steps 1. Open the sign in page. 2. Enter your account credentials. 3. Complete any verification step. 4. Arrive at your dashboard. ### Notes - The last used workspace is selected automatically. - You can switch workspaces from the switcher later. > [!INFO] Use the password reset flow if you cannot sign in. ## Sign out Sign out to end your current session. ### Where to find it - Account menu in the dashboard header ### Steps 1. Open the account menu. 2. Select Sign out. 3. Confirm when prompted. ### Notes - Signing out clears session cookies on this device. - You will need to sign in again to access the dashboard. > [!INFO] Signing out does not delete your data. ## First workspace created automatically If you sign in for the first time and you do not have any workspaces, Hoko creates one for you. ### What is created - A workspace named Default Workspace. - A default collection named Links. - You are assigned the owner role. ### What to do next 1. Rename the workspace to match your project. 2. Create additional collections as needed. 3. Update your profile and workspace images. > [!INFO] This happens only when no workspaces exist on your account. ## Workspace switching behavior Switching workspaces changes which collections and links you can access. ### How switching works - Use the workspace switcher in the dashboard header. - The selection is stored in cookies. - If the current collection is not available, the default collection is selected. ### What changes - Visible links and collections. - Analytics scope. - Default collection in forms. > [!INFO] If you lose access to a collection, another accessible collection is selected automatically. ## Role model explained Roles control what you can do inside each collection. ### Roles - Owner: full access including workspace settings and member management. - Admin: manage collections and content, invite members. - Editor: create and edit links, tags, partners, and templates. - Viewer: read-only access to links and analytics. - No access: the collection is hidden. ### Scope Roles are assigned per collection. You can be a viewer in one collection and an editor in another. > [!WARNING] Only owners can change workspace identity settings. ## Troubleshooting Use these checks when the dashboard does not load or access is missing. ### Missing dashboard access - Sign in again to refresh your session. - Verify you are a member of a workspace. - Ask an owner to send a new invite. ### Workspace not found - Open the workspace switcher and choose another workspace. - Clear cookies and sign in again. - Contact a workspace owner to confirm membership. ### Session expired - Reload the page. - Sign in again if needed. --- ### Profile Canonical URL: https://hoko.to/help/profile # Profile ## Update profile name Update the name shown in menus, invites, and activity. ### Where to find it - Dashboard → Settings → Profile ### Steps 1. Open Profile settings. 2. Edit your profile name. 3. Save the changes. ### Notes - Profile names must be 3–32 characters. - Names must be unique. > [!INFO] Your email address does not change. ## Update profile avatar Upload a new avatar image for your profile. ### Where to find it - Dashboard → Settings → Profile ### Steps 1. Open Profile settings. 2. Choose a new image file. 3. Wait for the upload to finish. 4. Save the changes. ### Notes - PNG, JPG, or SVG files only. - Maximum size is 2 MB. - The image is stored as a hashed file name. > [!INFO] If no avatar is set, initials are shown instead. ## Profile image rules Profile images follow these rules to keep storage consistent. ### Rules - Allowed types: PNG, JPG, or SVG. - Maximum file size is 2 MB. - File name uses a SHA256 hash and is stored as hash.png. - Storage path is avatar/{profileId}/hash.png. - The database stores only hash.png. ### Tips - Use a square image for best results. - Keep the file size small for faster upload. > [!INFO] The server builds the full image URL and sends it to the client. ## Troubleshooting Use these fixes when profile updates fail. ### Failed to update profile name - Check the 3–32 character length. - Ensure the name is unique. - Try saving again. ### Failed to update profile avatar - Confirm the file type is PNG, JPG, or SVG. - Keep the file under 2 MB. - Retry the upload. ### Invalid image type - Choose a PNG, JPG, or SVG file. - Avoid unsupported formats like GIF. ### Image size exceeds limit - Compress or resize the image. - Upload again after reducing size. --- ### Workspaces Canonical URL: https://hoko.to/help/workspaces # Workspaces ## Create a workspace Create a workspace to organize a new team or project. ### Where to find it - Workspace switcher in the dashboard header ### Steps 1. Open the workspace switcher. 2. Select Create a new workspace. 3. Enter a workspace name. 4. Confirm to create the workspace. ### Notes - A default collection named Links is created automatically. - You become the owner of the new workspace. - The new workspace becomes active right away. > [!WARNING] Creation is blocked if you reach the ownership limit. ## Workspace name rules Workspace names keep your switcher readable and organized. ### Rules - Name is required. - Maximum length is 32 characters. ### Examples - Marketing - Client A - Product Team ### Tips - Keep names short for easier switching. ## Workspace slug rules Workspace slugs appear in URLs and must be unique. ### Rules - Slug is required. - Length must be between 3 and 32 characters. - Slug must be unique across workspaces. ### Tips - Use lowercase letters, numbers, and hyphens for clarity. ## Update workspace name Update the workspace name to keep it recognizable in the switcher. ### Where to find it - Dashboard → Settings → Workspace ### Steps 1. Open Workspace settings. 2. Edit the workspace name. 3. Save the changes. ### Notes - Maximum length is 32 characters. ## Update workspace slug Update the workspace slug to change its URL identifier. ### Where to find it - Dashboard → Settings → Workspace ### Steps 1. Open Workspace settings. 2. Edit the workspace slug. 3. Save the changes. ### Notes - Slug length must be 3–32 characters. - Slug must be unique. ## Update workspace image Upload a new image to represent the workspace. ### Where to find it - Dashboard → Settings → Workspace ### Steps 1. Open Workspace settings. 2. Choose a new image file. 3. Wait for the upload to finish. 4. Save the changes. ### Notes - PNG, JPG, or SVG files only. - Maximum size is 2 MB. - The image is stored as a hashed file name. ## Workspace image rules Workspace images follow these rules to keep storage consistent. ### Rules - Allowed types: PNG, JPG, or SVG. - Maximum file size is 2 MB. - File name uses a SHA256 hash and is stored as hash.png. - Storage path is avatar/{workspaceId}/hash.png. - The database stores only hash.png. ### Tips - Use a square image for consistent avatars. - Keep the file size small for faster upload. ## Workspace ownership limits Workspaces have ownership limits to keep accounts manageable. ### Default limit - The default policy allows up to two owned workspaces per profile. ### What happens at the limit - Creating new workspaces is blocked. - You can still be invited to other workspaces. > [!INFO] Transfer ownership if you need to stay within the limit. ## Troubleshooting Use these fixes when workspace actions fail. ### Failed to create workspace - Check the workspace name length. - Confirm you have not reached the ownership limit. - Try again after refreshing. ### Workspace name rejected - Keep the name under 32 characters. - Avoid empty names. ### Workspace slug rejected - Use 3–32 characters. - Pick a unique slug. ### Failed to update workspace image - Use PNG, JPG, or SVG. - Keep the file under 2 MB. - Retry the upload. --- ### Collections Canonical URL: https://hoko.to/help/collections # Collections ## Collection concept A collection groups related links and controls access for each participant. ### Why it matters - Organize links by campaign, client, or product. - Control who can view or edit links. - Scope analytics to specific collections. ### How it connects - Every link belongs to one collection. - Participant roles are set per collection. > [!INFO] Collections keep access and analytics consistent across the workspace. ## Create a collection Create a collection to organize links in your workspace. ### Where to find it - Dashboard → Library → Collections ### Steps 1. Open Collections. 2. Select New collection. 3. Enter a collection name. 4. Create the collection. ### Notes - Names must be unique within the workspace. - Plan limits may apply. > [!INFO] Owners are granted access automatically. ## Collection name rules Collection names keep your library organized. ### Rules - Name is required. - Maximum length is 32 characters. - Names must be unique in the workspace. ### Tips - Use descriptive names that match your campaigns. ## Rename a collection Rename a collection to keep labels consistent. ### Where to find it - Dashboard → Library → Collections ### Steps 1. Open Collections. 2. Select Rename on a collection card. 3. Enter the new name. 4. Save the changes. ### Notes - Names must remain unique within the workspace. - Links stay in the same collection. ## Set default collection Choose which collection is selected by default. ### Where to find it - Dashboard → Library → Collections ### Steps 1. Open Collections. 2. Click Default on the chosen collection. ### Notes - The default button is disabled for the current default collection. - The default is used when switching workspaces. > [!INFO] If the default collection is removed, another collection is selected automatically. ## Remove a collection Remove a collection and its access from the workspace. ### Where to find it - Dashboard → Library → Collections ### Steps 1. Open Collections. 2. Select Remove on the collection card. 3. Confirm the removal. ### Notes - Links in the collection are removed from active views. - Members lose access to that collection. - The last remaining collection cannot be removed. > [!WARNING] Removal affects links, invites, and analytics for that collection. ## Troubleshooting Use these fixes when collection actions fail. ### Failed to create collection - Confirm you have permission to create collections. - Check name length and uniqueness. - Verify plan limits. ### Duplicate collection name - Choose a different name. - Search for existing collections with the same name. ### Failed to rename collection - Ensure the new name is unique. - Keep the name under 32 characters. ### Failed to set default collection - Try selecting another collection. - Refresh the page and retry. ### Failed to remove collection - Make sure it is not the only collection. - Confirm you have delete permissions. --- ### Links Canonical URL: https://hoko.to/help/links # Links ## Link concept A link is a short URL that redirects to a destination and tracks activity. ### Why it matters - Measure clicks, leads, and sales. - Organize links inside collections. ### How it connects - Each link belongs to one collection. - Links can include tags, partners, and UTM data. > [!INFO] Analytics are scoped to the collection that owns the link. ## Create a link Create a new link to share and track destinations. ### Where to find it - Dashboard → Links ### Steps 1. Open Links. 2. Select New link. 3. Enter the destination URL. 4. Choose a collection and optional settings. 5. Create the link. ### Notes - Destination URL and collection are required. - You can add tags, partners, or UTM data later. ## Required link fields Links require a few fields before they can be created. ### Required fields - Destination URL - Collection ### Optional fields - Short ID - Tags - Partner - UTM parameters - Comments - QR code - Targeting - Expiry rules > [!INFO] If you leave the short ID empty, a random one is generated. ## Edit a link Edit link details without losing historical analytics. ### Where to find it - Dashboard → Links ### Steps 1. Open Links. 2. Select a link from the list. 3. Edit the fields you need. 4. Save the changes. ### Notes - Edits apply immediately. - Past analytics remain tied to the link. ## Remove a link Remove a link so it no longer appears in the workspace. ### Where to find it - Dashboard → Links ### Steps 1. Open Links. 2. Select the link to remove. 3. Confirm deletion. ### Notes - Removed links no longer appear in lists. - Historical analytics remain available. ## Link filters Filters narrow the links list to what you need. ### Available filters - Tags - Collections - Destinations - Sort order ### How filters combine Multiple filters are applied together. Clear filters to return to the full list. ## Link search behavior Search scans key fields to help you find links fast. ### Search coverage - Link ID - Link name - Description - Short ID - Destination URL - Comments ### Search tips - Use partial keywords. - Search is case-insensitive. ## Link tags usage Tags help categorize links and improve filtering. ### When to use tags - Group links across collections. - Filter analytics by campaign or topic. ### How tags are applied Select tags when creating or editing a link. You can add or remove tags any time. > [!INFO] Tag changes apply immediately to filters and analytics views. ## Link partners usage Partners connect links to affiliates or collaborators. ### When to use partners - Track partner-driven traffic. - Attribute conversions to a partner. ### How partners are applied Select a partner when creating or editing a link. Partners are optional. ## Link UTM templates usage UTM templates fill tracking parameters quickly. ### When to use UTM templates - Reuse consistent UTM values across campaigns. - Reduce manual entry when creating links. ### How templates are applied Select a template in the link dialog to populate UTM fields. You can edit values after applying. ## Troubleshooting Use these fixes when link actions fail. ### Failed to create link - Check the destination URL. - Select an accessible collection. - Verify permissions. ### Invalid destination URL - Include the full URL with https://. - Avoid spaces or unsupported characters. ### Link not found - Clear filters and search again. - Verify you have access to the collection. ### Failed to update link - Check required fields. - Retry after refreshing the page. ### Failed to delete link - Confirm you have delete permission. - Try again after refreshing. ### Tag assignment failed - Check that the tag still exists. - Try removing and re-adding the tag. ### Partner assignment failed - Confirm the partner exists. - Retry after refreshing. --- ### Tags Canonical URL: https://hoko.to/help/tags # Tags ## Tag concept Tags label links so you can filter and analyze them easily. ### Why it matters - Filter links by campaign or theme. - Use tags to segment analytics. ### How it connects - Tags can be applied to any link. - Analytics filters can use tags. ## Create a tag Create tags to organize links and analytics. ### Where to find it - Dashboard → Library → Tags ### Steps 1. Open Tags. 2. Select New tag. 3. Enter a tag name and color. 4. Create the tag. ### Notes - Tag names must be unique. - If the name is empty, a short ID is generated. ## Update a tag Update tag names or colors to keep them consistent. ### Where to find it - Dashboard → Library → Tags ### Steps 1. Open Tags. 2. Select a tag. 3. Edit the name or color. 4. Save the changes. ### Notes - Names must stay unique. ## Remove a tag Remove a tag when you no longer need it. ### Where to find it - Dashboard → Library → Tags ### Steps 1. Open Tags. 2. Select Remove on the tag. 3. Confirm deletion. ### Notes - Removing a tag does not delete links. - Links will simply lose that tag. ## Tag color rules Tag colors help you recognize categories at a glance. ### Rules - Choose from the available color palette. - Use high-contrast colors for readability. ### Tips - Keep a consistent color scheme across similar tags. ## Troubleshooting Use these fixes when tag actions fail. ### Failed to create tag - Check tag name uniqueness. - Verify you have permission to create tags. ### Duplicate tag name - Use a different name. - Search existing tags first. ### Invalid tag color - Select a color from the provided palette. ### Failed to update tag - Ensure the new name is unique. - Retry after refreshing. ### Failed to remove tag - Confirm you have delete permission. - Try again after refreshing. --- ### Partners Canonical URL: https://hoko.to/help/partners # Partners ## Partner concept Partners represent affiliates or collaborators tied to links. ### Why it matters - Attribute traffic to partners. - Measure partner performance. ### How it connects - A link can reference one partner. - Partners are optional for links. ## Create a partner Create a partner to track affiliate or collaborator links. ### Where to find it - Dashboard → Partners ### Steps 1. Open Partners. 2. Select New partner. 3. Enter the partner name. 4. Save the partner. ### Notes - Partner name is required. - Email and phone are optional. ## Update a partner Update partner details to keep records accurate. ### Where to find it - Dashboard → Partners ### Steps 1. Open Partners. 2. Select a partner. 3. Edit details. 4. Save changes. ## Remove a partner Remove a partner when it is no longer needed. ### Where to find it - Dashboard → Partners ### Steps 1. Open Partners. 2. Select Remove on the partner. 3. Confirm deletion. ### Notes - Removing a partner does not delete links. - Links will lose the partner association. ## Troubleshooting Use these fixes when partner actions fail. ### Failed to create partner - Ensure the partner name is provided. - Retry after refreshing. ### Failed to update partner - Retry after refreshing. - Check required fields. ### Failed to remove partner - Confirm you have delete permission. - Try again after refreshing. --- ### UTM templates Canonical URL: https://hoko.to/help/utm-templates # UTM templates ## UTM template concept UTM templates store reusable tracking parameters for links. ### Why it matters - Keep campaign tracking consistent. - Reduce manual entry when creating links. ### How it connects - Templates can be applied in link dialogs. - Templates do not change existing links unless applied. ## Create a UTM template Create a template to reuse UTM parameters. ### Where to find it - Dashboard → Library → UTM Templates ### Steps 1. Open UTM Templates. 2. Select New template. 3. Enter template name and UTM fields. 4. Save the template. ### Notes - Template name is required and must be unique. - Provide at least one UTM field. - Only fill the UTM fields you need. ## Update a UTM template Update template values to keep tracking accurate. ### Where to find it - Dashboard → Library → UTM Templates ### Steps 1. Open UTM Templates. 2. Select a template. 3. Edit fields and save. ## Remove a UTM template Remove a template you no longer need. ### Where to find it - Dashboard → Library → UTM Templates ### Steps 1. Open UTM Templates. 2. Select Remove on the template. 3. Confirm deletion. ### Notes - Removing a template does not change existing links. ## Troubleshooting Use these fixes when template actions fail. ### Failed to create UTM template - Check that the name is unique. - Retry after refreshing. ### Duplicate template name - Choose a different template name. ### Failed to update UTM template - Retry after refreshing. - Check required fields. ### Failed to remove UTM template - Confirm you have delete permission. - Try again after refreshing. --- ### Analytics Canonical URL: https://hoko.to/help/analytics # Analytics ## Analytics overview Analytics show how your links perform over time. ### What you can see - Clicks - Leads - Sales - Top referrers - UTM breakdowns - Tag performance ### Data window By default analytics load the last 30 days and are limited by your plan event caps. > [!INFO] Analytics are scoped to collections you can access. ## Analytics scope by collection Analytics only include collections you have access to. ### Access rules - Viewer or higher access is required to see analytics. - Blocked or removed participants do not see analytics. - If you have no accessible collections, analytics are empty. ## Click analytics explained Click analytics record every visit to a link. ### Captured details - Destination - Referrer - UTM parameters - Device and browser - Location ### How to use - Spot top-performing campaigns. - Understand traffic sources. ## Lead analytics explained Lead analytics show conversions captured as leads. ### Lead events - Recorded when a lead is tracked. - Linked to a click and a link. ### How to use - Measure campaign quality. - Compare clicks to leads for conversion rate. ## Sale analytics explained Sale analytics track revenue events tied to links. ### Sale events - Recorded when a sale is tracked. - Includes amount and currency when provided. ### How to use - Measure ROI per campaign. - Compare revenue across collections. ## Filter behavior Analytics filters refine what data you see. ### Filter effects - Filters apply to charts and tables. - Multiple filters combine together. - Clear filters to reset the view. ## Export analytics data Export analytics to CSV for reporting. ### How export works - Exports reflect current filters. - Includes recent data from the analytics window. ### Tips - Apply filters before exporting. - Use a spreadsheet to build charts. ## Troubleshooting Use these fixes when analytics does not load or looks incomplete. ### Missing analytics data - Confirm you have activity in the last 30 days. - Check collection access. ### Access denied to analytics - Ask an owner to grant viewer access. - Switch to a collection you can access. ### Export failed - Try exporting with fewer filters. - Reload the page and retry. ### Slow analytics load - Apply filters to reduce data volume. - Refresh and try again later. --- ### Participants Canonical URL: https://hoko.to/help/participants # Participants ## Participant roles explained Participant roles control access per collection. ### Roles - Owner: full access and workspace management. - Admin: manage collections and invite members. - Editor: create and edit links and content. - Viewer: read-only access. - No access: hidden from the member. ### Scope Roles are assigned per collection and can differ across collections. ## Invite a participant Invite a participant and choose their collection roles. ### Where to find it - Dashboard → Settings → Participants ### Steps 1. Open Participants settings. 2. Enter the member email. 3. Assign roles for collections. 4. Send the invite. ### Notes - Invites are scoped to collections. - Members only see collections they are assigned to. ## Assign collection roles Assign different roles per collection for the same member. ### Where to find it - Dashboard → Settings → Participants ### Steps 1. Open Participants settings. 2. Select a member or invite form. 3. Choose a role for each collection. 4. Save the changes. ### Notes - Setting a role to none hides the collection for that member. ## Resend an invite Resend an invite if the email was missed. ### Where to find it - Dashboard → Settings → Participants ### Steps 1. Open Participants settings. 2. Find the pending invite. 3. Select Resend invite. ### Notes - Only pending invites can be resent. ## Revoke an invite Revoke an invite to stop it from being accepted. ### Where to find it - Dashboard → Settings → Participants ### Steps 1. Open Participants settings. 2. Find the pending invite. 3. Select Revoke invite. ### Notes - Revoked invites cannot be accepted. ## Update member roles Change a member role for one or more collections. ### Where to find it - Dashboard → Settings → Participants ### Steps 1. Open Participants settings. 2. Select the member. 3. Adjust roles per collection. 4. Save the changes. ### Notes - Keep at least one owner in the workspace. ## Remove a participant Remove a participant from the workspace. ### Where to find it - Dashboard → Settings → Participants ### Steps 1. Open Participants settings. 2. Select the member. 3. Choose Remove participant. 4. Confirm removal. > [!WARNING] You cannot remove the last remaining owner. ## Transfer ownership Transfer ownership by promoting another member to owner. ### Where to find it - Dashboard → Settings → Participants ### Steps 1. Open Participants settings. 2. Select the member. 3. Assign the owner role. 4. Confirm the change. ### Notes - Keep at least one owner at all times. ## Troubleshooting Use these fixes when participant actions fail. ### Invite not delivered - Check the email address. - Ask the invitee to check spam. - Resend the invite. ### Invite already exists - Check pending invites. - Revoke and resend if needed. ### Role assignment failed - Ensure you have admin or owner permissions. - Retry after refreshing. ### Ownership transfer blocked - Make sure at least one owner remains. - Confirm the target member has access. ### Failed to remove participant - Ensure you are not removing the last owner. - Retry after refreshing. --- ### API keys Canonical URL: https://hoko.to/help/api-keys # API keys ## API key concept API keys allow programmatic access to your workspace data. ### Why it matters - Integrate with external tools. - Control access through scopes. ### How it connects - Each key has read and write scopes. - Keys are tied to the workspace. - Allowed hostnames can limit browser usage. > [!INFO] Treat API keys like passwords. ## Create an API key Create a key for integrations and automation. ### Where to find it - Dashboard → Integrations → API Keys ### Steps 1. Open API Keys. 2. Select New key. 3. Choose scopes. 4. Add allowed hostnames if needed. 5. Create and copy the key. ### Notes - You may only see the key value once. - Store it securely. ## API key scopes explained Scopes control what an API key can access. ### Scope types - Read: view data - Write: create or update data ### Best practice - Grant only the scopes you need. - Use separate keys per integration. ## Allowed hostnames configuration Limit browser usage by restricting where a key can be used. ### How it works - Hoko checks the Origin or Referer hostname when present. - Requests without those headers are not filtered by hostname. - Leave the list empty to allow any hostname. ### Examples - `example.com` - `api.example.com` - `*.example.com` ## Update API key settings Adjust the name, scopes, and allowed hostnames. ### Where to find it - Dashboard → Integrations → API Keys ### Steps 1. Open API Keys. 2. Select the key. 3. Update the fields. 4. Save changes. ## Revoke an API key Revoke a key to stop its access immediately. ### Where to find it - Dashboard → Integrations → API Keys ### Steps 1. Open API Keys. 2. Select the key. 3. Choose Revoke. 4. Confirm the action. > [!WARNING] Revoking a key will break integrations using it. ## Troubleshooting Use these fixes when API key actions fail. ### Failed to create API key - Check required scopes. - Retry after refreshing. ### Missing required scopes - Select at least one scope. ### Invalid API key - Verify you copied the full key. - Generate a new key if needed. - Check allowed hostnames when calling from a browser. ### Hostname blocked - Ensure the request Origin or Referer matches an allowed hostname. - Add the hostname to the allowlist and try again. ### API key revoked - Create a new key and update your integration. --- ### Billing Canonical URL: https://hoko.to/help/billing # Billing ## Current plan View your current plan and usage limits. ### Where to check - Dashboard → Settings → Subscription ### What you will see - Plan name - Usage counters - Billing status ## Usage limits explained Usage limits define how many resources you can use per cycle. ### Typical limits - Links - Events - Collections - Tags - Users ### Cycle reset Usage resets every billing cycle. You can track the cycle dates in the usage panel. ## Upgrade a plan Upgrade to increase limits and access more features. ### Where to find it - Dashboard → Settings → Subscription ### Steps 1. Open the Subscription page. 2. Choose a higher plan. 3. Complete checkout. ## Downgrade a plan Downgrade if you no longer need higher limits. ### Where to find it - Dashboard → Settings → Subscription ### Steps 1. Open the Subscription page. 2. Choose a lower plan. 3. Confirm the change. > [!WARNING] If your usage exceeds the new limits, some actions may be blocked. ## Troubleshooting Use these fixes when billing updates fail. ### Checkout failed - Verify payment details. - Try again with a different card. ### Plan not updated - Refresh the page. - Wait a few minutes and retry. ### Limits not refreshed - Sign out and sign in again. - Contact support if it persists. --- ### Storage Canonical URL: https://hoko.to/help/storage # Storage ## Image upload flow Images are uploaded in a secure flow that uses signed URLs. ### Upload steps 1. The client hashes the image with SHA256. 2. The server issues a signed upload URL. 3. The image is uploaded to the avatar bucket. 4. The server stores the hash file name in the database. ## Image hashing rules Hashing ensures consistent storage and naming. ### Hash requirements - SHA256 hash of the file contents. - File name is hash.png. - Bucket is avatar. ### Folder rules - Profile images use folder {profileId}. - Workspace images use folder {workspaceId}. ## Avatar URL behavior Image URLs are built on the server and sent to the client. ### How URLs are built - The database stores only hash.png. - The server builds /storage/avatar/{folderId}/hash.png. ### Fallbacks - If an image fails to load, initials are shown. - If no image is set, initials are used by default. ## Troubleshooting Use these fixes when image uploads fail. ### Failed to generate upload URL - Retry the upload. - Check that the file hash is valid. ### Storage resource not found - Confirm the bucket name is avatar. - Ensure the folder ID matches the profile or workspace ID. ### Upload rejected - Use PNG, JPG, or SVG. - Keep the file under 2 MB. ### Image not visible - Refresh the page. - Check that the image URL is reachable. --- ### Security Canonical URL: https://hoko.to/help/security # Security ## Session lifecycle Sessions keep you signed in across visits. ### Session behavior - Sessions are stored in cookies. - Tokens refresh automatically when possible. - Signing out clears the session. ## Cookie workspace selection Your active workspace and collection are stored in cookies. ### What is stored - currentWorkspaceId - currentCollectionId ### How it is used - Restores your last selection on next visit. - Falls back to default collection if needed. ## Access removal behavior When access is removed, your permissions update immediately. ### What happens - Collections you no longer access disappear. - The system selects another accessible collection. - If no access remains, you are redirected to sign in. ## Troubleshooting Use these fixes when security-related access issues occur. ### Session invalid - Sign in again. - Clear cookies if the issue persists. ### Access revoked - Contact a workspace owner to restore access. - Check your invite status. ### Workspace not accessible - Switch to another workspace. - Sign out and back in. ---