feat: openapi swagger ui
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
name: Deploy API Documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- 'docs/openapi.yaml'
|
||||||
|
- '.github/workflows/deploy-docs.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate Swagger UI
|
||||||
|
uses: Legion2/swagger-ui-action@v1
|
||||||
|
with:
|
||||||
|
output: swagger-ui
|
||||||
|
spec-file: docs/openapi.yaml
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: swagger-ui
|
||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
# API Reference
|
|
||||||
|
|
||||||
1. **Ping the API**
|
|
||||||
|
|
||||||
- Endpoint: `GET /api/ping`
|
|
||||||
- Payload: None
|
|
||||||
- Response: 200
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": "pong"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
2. **Redirect by Slug**
|
|
||||||
|
|
||||||
- Endpoint: `GET /:slug`
|
|
||||||
- Payload: None
|
|
||||||
- Response: 301
|
|
||||||
|
|
||||||
3. **List All Links**
|
|
||||||
|
|
||||||
- Endpoint: `GET /api/links`
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Query Parameters:
|
|
||||||
- `limit` (optional): Number of results per page (default: 100)
|
|
||||||
- `cursor` (optional): Pagination cursor from previous response
|
|
||||||
- Response: 200
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://monocuco.donado.co"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pagination": {
|
|
||||||
"has_more": true,
|
|
||||||
"next": "75e0a7f4-9c5e-1235-b546-eb9c5e40f7ac"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **List link by ID**
|
|
||||||
- Endpoint: `GET /api/links/:id`
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Payload: None
|
|
||||||
- Note: This endpoint returns up to 100 of the most recent clicks. For complete click history, use the `/api/links/:id/clicks` endpoint with pagination.
|
|
||||||
- Response: 200
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://monocuco.donado.co",
|
|
||||||
"clicks": [
|
|
||||||
{
|
|
||||||
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
|
||||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
|
||||||
"country": "DE",
|
|
||||||
"browser": "Firefox",
|
|
||||||
"os": "Mac OS X",
|
|
||||||
"referer": "Direct",
|
|
||||||
"created_at": "2024-07-12T19:25:22Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **List Clicks for a Link**
|
|
||||||
- Endpoint: `GET /api/links/:id/clicks`
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Query Parameters:
|
|
||||||
- `limit` (optional): Number of results per page (default: 100)
|
|
||||||
- `cursor` (optional): Pagination cursor from previous response
|
|
||||||
- Response: 200
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
|
||||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
|
||||||
"country": "DE",
|
|
||||||
"browser": "Firefox",
|
|
||||||
"os": "Mac OS X",
|
|
||||||
"referer": "Direct",
|
|
||||||
"created_at": "2024-07-12T19:25:22Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pagination": {
|
|
||||||
"has_more": true,
|
|
||||||
"next": "629e3301-47f8-389b-b24c-f1c561df9825"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Create new link**
|
|
||||||
- Endpoint: `POST /api/links`
|
|
||||||
- Payload:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"url": "https://example.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Response: 201
|
|
||||||
- Response Example:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://example.com",
|
|
||||||
"clicks": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
7. **Update an existing link by ID**
|
|
||||||
- Endpoint: `PUT /api/links/:id`
|
|
||||||
- Payload:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"url": "https://newexample.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Response: 200
|
|
||||||
- Response Example:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://newexample.com",
|
|
||||||
"clicks": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
8. **Delete a link by ID**
|
|
||||||
- Endpoint: `DELETE /api/links/:id`
|
|
||||||
- Payload: None
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Response: 204
|
|
||||||
@@ -0,0 +1,496 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: Bit - URL Shortener API
|
||||||
|
description: A high-performance URL shortener service with click tracking and analytics
|
||||||
|
version: 1.0.0
|
||||||
|
contact:
|
||||||
|
name: API Support
|
||||||
|
url: https://github.com/sjdonado/bit
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:4000
|
||||||
|
description: Development server
|
||||||
|
- url: http://localhost:4001
|
||||||
|
description: Benchmark server
|
||||||
|
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/api/ping:
|
||||||
|
get:
|
||||||
|
summary: Ping the API
|
||||||
|
description: Health check endpoint to verify the API is running
|
||||||
|
operationId: ping
|
||||||
|
tags:
|
||||||
|
- Health
|
||||||
|
security: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: API is healthy
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
example: pong
|
||||||
|
|
||||||
|
/{slug}:
|
||||||
|
get:
|
||||||
|
summary: Redirect by slug
|
||||||
|
description: Redirects to the original URL and tracks the click asynchronously
|
||||||
|
operationId: redirectBySlug
|
||||||
|
tags:
|
||||||
|
- Redirects
|
||||||
|
security: []
|
||||||
|
parameters:
|
||||||
|
- name: slug
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The short URL slug
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 3wP4BQ
|
||||||
|
- name: utm_source
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: UTM source parameter for tracking
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: email_campaign
|
||||||
|
responses:
|
||||||
|
'301':
|
||||||
|
description: Redirect to original URL
|
||||||
|
headers:
|
||||||
|
Location:
|
||||||
|
description: The original URL
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: https://example.com
|
||||||
|
X-Forwarded-For:
|
||||||
|
description: Client IP address
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
User-Agent:
|
||||||
|
description: User agent string
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'404':
|
||||||
|
description: Link not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
|
/api/links:
|
||||||
|
get:
|
||||||
|
summary: List all links
|
||||||
|
description: Retrieve all links for the authenticated user with pagination support
|
||||||
|
operationId: listLinks
|
||||||
|
tags:
|
||||||
|
- Links
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Number of results per page
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 100
|
||||||
|
minimum: 1
|
||||||
|
maximum: 1000
|
||||||
|
- name: cursor
|
||||||
|
in: query
|
||||||
|
description: Pagination cursor from previous response
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: List of links
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/LinkSummary'
|
||||||
|
pagination:
|
||||||
|
$ref: '#/components/schemas/Pagination'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
|
post:
|
||||||
|
summary: Create new link
|
||||||
|
description: Create a new shortened link
|
||||||
|
operationId: createLink
|
||||||
|
tags:
|
||||||
|
- Links
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- url
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
description: The URL to shorten
|
||||||
|
example: https://example.com
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Link created successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/components/schemas/Link'
|
||||||
|
'400':
|
||||||
|
description: Bad request - invalid URL or missing field
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
examples:
|
||||||
|
missingField:
|
||||||
|
value:
|
||||||
|
error: "url: Required field"
|
||||||
|
invalidUrl:
|
||||||
|
value:
|
||||||
|
errors:
|
||||||
|
url:
|
||||||
|
- is invalid
|
||||||
|
'401':
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
|
/api/links/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get link by ID
|
||||||
|
description: Retrieve a specific link with up to 100 most recent clicks. For complete click history, use /api/links/{id}/clicks
|
||||||
|
operationId: getLink
|
||||||
|
tags:
|
||||||
|
- Links
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Link ID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Link details
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/components/schemas/Link'
|
||||||
|
'404':
|
||||||
|
description: Link not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
|
put:
|
||||||
|
summary: Update link
|
||||||
|
description: Update the URL of an existing link
|
||||||
|
operationId: updateLink
|
||||||
|
tags:
|
||||||
|
- Links
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Link ID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- url
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
description: The new URL
|
||||||
|
example: https://newexample.com
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Link updated successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/components/schemas/Link'
|
||||||
|
'400':
|
||||||
|
description: Bad request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'403':
|
||||||
|
description: Forbidden - link belongs to another user
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'404':
|
||||||
|
description: Link not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
|
delete:
|
||||||
|
summary: Delete link
|
||||||
|
description: Delete a link and all its associated clicks
|
||||||
|
operationId: deleteLink
|
||||||
|
tags:
|
||||||
|
- Links
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Link ID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Link deleted successfully
|
||||||
|
'401':
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'403':
|
||||||
|
description: Forbidden - link belongs to another user
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'404':
|
||||||
|
description: Link not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
|
/api/links/{id}/clicks:
|
||||||
|
get:
|
||||||
|
summary: List clicks for a link
|
||||||
|
description: Retrieve all clicks for a specific link with pagination support
|
||||||
|
operationId: listClicks
|
||||||
|
tags:
|
||||||
|
- Clicks
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Link ID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Number of results per page
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 100
|
||||||
|
minimum: 1
|
||||||
|
maximum: 1000
|
||||||
|
- name: cursor
|
||||||
|
in: query
|
||||||
|
description: Pagination cursor from previous response
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: List of clicks
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Click'
|
||||||
|
pagination:
|
||||||
|
$ref: '#/components/schemas/Pagination'
|
||||||
|
'401':
|
||||||
|
description: Unauthorized
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
'404':
|
||||||
|
description: Link not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
ApiKeyAuth:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: X-Api-Key
|
||||||
|
description: API key for authentication
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
LinkSummary:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Unique link identifier
|
||||||
|
example: 1
|
||||||
|
refer:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
description: The shortened URL
|
||||||
|
example: http://localhost:4000/3wP4BQ
|
||||||
|
origin:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
description: The original URL
|
||||||
|
example: https://monocuco.donado.co
|
||||||
|
|
||||||
|
Link:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/LinkSummary'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
clicks:
|
||||||
|
type: array
|
||||||
|
description: Array of click records (up to 100 most recent)
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Click'
|
||||||
|
|
||||||
|
Click:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Unique click identifier
|
||||||
|
example: 1
|
||||||
|
user_agent:
|
||||||
|
type: string
|
||||||
|
description: User agent string
|
||||||
|
example: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0
|
||||||
|
country:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Country code (ISO 3166-1 alpha-2)
|
||||||
|
example: US
|
||||||
|
browser:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Browser name
|
||||||
|
example: Firefox
|
||||||
|
os:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Operating system
|
||||||
|
example: Mac OS X
|
||||||
|
referer:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Referer domain or utm_source
|
||||||
|
example: Direct
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Click timestamp
|
||||||
|
example: 2024-07-12T19:25:22Z
|
||||||
|
|
||||||
|
Pagination:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
has_more:
|
||||||
|
type: boolean
|
||||||
|
description: Whether there are more results
|
||||||
|
example: true
|
||||||
|
next:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
nullable: true
|
||||||
|
description: Cursor for next page (link/click ID)
|
||||||
|
example: 12
|
||||||
|
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
description: Error message
|
||||||
|
example: Resource not found
|
||||||
|
required:
|
||||||
|
- error
|
||||||
|
|
||||||
|
ValidationErrors:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
errors:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Field-level validation errors
|
||||||
|
example:
|
||||||
|
url:
|
||||||
|
- is invalid
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: Health
|
||||||
|
description: Health check endpoints
|
||||||
|
- name: Redirects
|
||||||
|
description: URL redirection and click tracking
|
||||||
|
- name: Links
|
||||||
|
description: Link management operations
|
||||||
|
- name: Clicks
|
||||||
|
description: Click analytics and tracking
|
||||||
Reference in New Issue
Block a user