เริ่มต้นใช้งาน
ทุกคำขอต้องแนบ Bearer token ของ Sanctum และตั้ง header Accept: application/json ทุกครั้ง โทเคนสร้างได้ที่หน้า Profile → API Tokens ในระบบ (ระบบจะแสดงค่าโทเคนแบบเต็มเพียงครั้งเดียวตอนสร้าง)
Base URL
https://app.isref.cc/api/v1
Header ที่ต้องส่ง
Authorization: Bearer <token> Accept: application/json Content-Type: application/json # POST / PUT / PATCH
ผู้ใช้ทั่วไปเห็นและจัดการได้เฉพาะลิงก์ของตนเอง โทเคนของผู้ดูแลระบบจัดการลิงก์ของทุกคนได้
รูปแบบผลลัพธ์ (Response envelope)
ทุกผลลัพธ์ ทั้งสำเร็จและผิดพลาด (รวมถึง exception จากเฟรมเวิร์ก) ใช้โครงสร้างเดียวกัน
สำเร็จ
{
"success": true,
"data": { … },
"message": "Short link created."
}
ผิดพลาด
{
"success": false,
"data": null,
"message": "This action is unauthorized.",
"errors": {}
}
รหัสสถานะและข้อผิดพลาด
| HTTP | เกิดเมื่อ |
|---|---|
200 | อ่านข้อมูล หรือแก้ไขสำเร็จ |
201 | สร้างลิงก์ใหม่สำเร็จ |
204 | ลบลิงก์สำเร็จ (ไม่มีเนื้อหาตอบกลับ) |
401 | ไม่มีโทเคน หรือโทเคนไม่ถูกต้อง/หมดอายุ |
403 | โทเคนไม่มีสิทธิ์ที่ต้องใช้ หรือลิงก์ไม่ใช่ของเจ้าของโทเคน |
404 | ไม่พบเส้นทาง หรือไม่พบ id ที่ระบุ |
422 | ข้อมูลไม่ผ่านการตรวจสอบ (ดูรายละเอียดใน errors) |
429 | เรียกเกินอัตราที่กำหนด (60 ครั้ง/นาที ต่อโทเคน) |
สิทธิ์ของโทเคน
สิทธิ์ถูกตรวจสอบที่ฝั่งเซิร์ฟเวอร์ ไคลเอนต์ไม่สามารถกำหนดสิทธิ์ของตัวเองได้ เลือกสิทธิ์ตอนสร้างโทเคนในระบบ
| Endpoint | สิทธิ์ที่ต้องมี |
|---|---|
GET /shortlinks |
shorturl:read |
GET /shortlinks/{id} |
shorturl:read |
POST /shortlinks |
shorturl:create |
PUT · PATCH /shortlinks/{id} |
shorturl:update |
DELETE /shortlinks/{id} |
shorturl:delete |
การจำกัดอัตรา
จำกัด 60 คำขอต่อนาที ต่อโทเคน (นับรวมกับ id ผู้ใช้) เมื่อเกินจะได้ 429 พร้อม header X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After
Endpoints
GET /shortlinks
คืนรายการลิงก์ของผู้ใช้แบบแบ่งหน้า เรียงจากใหม่ไปเก่า
พารามิเตอร์ query
| ฟิลด์ | คำอธิบาย |
|---|---|
q | ค้นหาจาก code, title หรือ destination_url |
status | กรองตามสถานะ: active หรือ disabled |
per_page | จำนวนต่อหน้า 1–100 (ค่าเริ่มต้น 15) |
page | หน้าที่ต้องการ (เริ่มที่ 1) |
ตัวอย่างคำขอ
curl "https://app.isref.cc/api/v1/shortlinks?status=active&per_page=20" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
import requests
resp = requests.get(
"https://app.isref.cc/api/v1/shortlinks?status=active&per_page=20",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
)
print(resp.status_code, resp.json())
<?php
$ch = curl_init("https://app.isref.cc/api/v1/shortlinks?status=active&per_page=20");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$token}",
"Accept: application/json",
],
]);
$response = curl_exec($ch);
printf("%d\n%s\n", curl_getinfo($ch, CURLINFO_HTTP_CODE), $response);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://app.isref.cc/api/v1/shortlinks?status=active&per_page=20", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}
const resp = await fetch("https://app.isref.cc/api/v1/shortlinks?status=active&per_page=20", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
console.log(resp.status, await resp.json());
ตัวอย่างผลลัพธ์
{
"success": true,
"data": {
"items": [
{
"id": 12,
"code": "launch",
"short_url": "https://isref.cc/launch",
"destination_url": "https://example.com/page",
"title": null,
"description": null,
"status": "active",
"has_password": false,
"available_from": null,
"is_pending": false,
"expires_at": null,
"is_expired": false,
"click_count": 34,
"last_clicked_at": "2026-09-01T12:00:00+00:00",
"owner": { "id": 1, "name": "Ada" },
"created_at": "2026-08-01T09:00:00+00:00",
"updated_at": "2026-08-01T09:00:00+00:00"
}
],
"meta": { "current_page": 1, "per_page": 15, "total": 1, "last_page": 1 }
},
"message": null
}
POST /shortlinks
สร้างลิงก์สั้นใหม่ ถ้าไม่ส่ง code ระบบจะสุ่มให้
เนื้อหาคำขอ (JSON)
| ฟิลด์ | ข้อกำหนด |
|---|---|
destination_url
จำเป็น
|
URL ปลายทาง · http/https เท่านั้น · ต้องมีโฮสต์ · ยาวไม่เกิน 2048 ตัวอักษร |
code
ไม่บังคับ
|
โค้ดกำหนดเอง · [A-Za-z0-9_-] ยาว 3–40 ตัว · ต้องไม่ซ้ำและไม่ใช่คำสงวน · เว้นว่างเพื่อให้สุ่มอัตโนมัติ |
title
ไม่บังคับ
|
ชื่อลิงก์ · ยาวไม่เกิน 255 |
description
ไม่บังคับ
|
คำอธิบาย · ยาวไม่เกิน 2000 |
status
ไม่บังคับ
|
active หรือ disabled (ค่าเริ่มต้น active) — disabled จะไม่ redirect |
available_from
ไม่บังคับ
|
วันเวลาที่เริ่มใช้งานได้ (ISO-8601) · ก่อนเวลานี้ลิงก์จะตอบ 404 “ยังไม่เปิดใช้งาน” |
expires_at
ไม่บังคับ
|
วันเวลาหมดอายุ (ISO-8601) · ต้องเป็นอนาคต และหลัง available_from · หลังเวลานี้ลิงก์ตอบ 410 |
password
ไม่บังคับ
|
รหัสผ่านสำหรับผู้เข้าชม · 4–255 ตัวอักษร · เก็บแบบแฮช ไม่คืนค่าผ่าน API |
ตัวอย่างคำขอ
curl -X POST "https://app.isref.cc/api/v1/shortlinks" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"destination_url":"https://example.com/linux-course","code":"linux-course"}'
import requests
resp = requests.post(
"https://app.isref.cc/api/v1/shortlinks",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
json={
"destination_url": "https://example.com/linux-course",
"code": "linux-course",
},
)
print(resp.status_code, resp.json())
<?php
$ch = curl_init("https://app.isref.cc/api/v1/shortlinks");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$token}",
"Accept: application/json",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"destination_url" => "https://example.com/linux-course",
"code" => "linux-course",
]),
]);
$response = curl_exec($ch);
printf("%d\n%s\n", curl_getinfo($ch, CURLINFO_HTTP_CODE), $response);
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
payload := strings.NewReader(`{"destination_url":"https://example.com/linux-course","code":"linux-course"}`)
req, _ := http.NewRequest("POST", "https://app.isref.cc/api/v1/shortlinks", payload)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}
const resp = await fetch("https://app.isref.cc/api/v1/shortlinks", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
destination_url: "https://example.com/linux-course",
code: "linux-course",
}),
});
console.log(resp.status, await resp.json());
ตัวอย่างผลลัพธ์ · 201
{
"success": true,
"data": {
"id": 41,
"code": "linux-course",
"short_url": "https://isref.cc/linux-course",
"destination_url": "https://example.com/linux-course",
"status": "active",
"has_password": false,
…
},
"message": "Short link created."
}
GET /shortlinks/{id}
คืนข้อมูลลิงก์เดียวตาม id
curl "https://app.isref.cc/api/v1/shortlinks/41" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
import requests
resp = requests.get(
"https://app.isref.cc/api/v1/shortlinks/41",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
)
print(resp.status_code, resp.json())
<?php
$ch = curl_init("https://app.isref.cc/api/v1/shortlinks/41");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$token}",
"Accept: application/json",
],
]);
$response = curl_exec($ch);
printf("%d\n%s\n", curl_getinfo($ch, CURLINFO_HTTP_CODE), $response);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://app.isref.cc/api/v1/shortlinks/41", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}
const resp = await fetch("https://app.isref.cc/api/v1/shortlinks/41", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
console.log(resp.status, await resp.json());
200 · โครงสร้างที่ทุก endpoint คืนกลับมาในฟิลด์ data (หรือ data.items สำหรับรายการ)
PUT PATCH /shortlinks/{id}
แก้ไขลิงก์ ส่งเฉพาะฟิลด์ที่ต้องการเปลี่ยน (PATCH) หรือส่งครบ (PUT) การเปลี่ยน code จะตรวจความซ้ำและคำสงวนใหม่
ส่ง password ค่าใหม่เพื่อตั้ง/เปลี่ยนรหัส · ส่ง remove_password: true เพื่อยกเลิกการป้องกัน · ไม่ส่งทั้งคู่ = คงค่าเดิม
| ฟิลด์ | ข้อกำหนด |
|---|---|
destination_url | URL ปลายทาง · http/https เท่านั้น · ต้องมีโฮสต์ · ยาวไม่เกิน 2048 ตัวอักษร |
code | โค้ดกำหนดเอง · [A-Za-z0-9_-] ยาว 3–40 ตัว · ต้องไม่ซ้ำและไม่ใช่คำสงวน · เว้นว่างเพื่อให้สุ่มอัตโนมัติ |
title | ชื่อลิงก์ · ยาวไม่เกิน 255 |
description | คำอธิบาย · ยาวไม่เกิน 2000 |
status | active หรือ disabled (ค่าเริ่มต้น active) — disabled จะไม่ redirect |
available_from | วันเวลาที่เริ่มใช้งานได้ (ISO-8601) · ก่อนเวลานี้ลิงก์จะตอบ 404 “ยังไม่เปิดใช้งาน” |
expires_at | วันเวลาหมดอายุ (ISO-8601) · ต้องเป็นอนาคต และหลัง available_from · หลังเวลานี้ลิงก์ตอบ 410 |
password | รหัสผ่านสำหรับผู้เข้าชม · 4–255 ตัวอักษร · เก็บแบบแฮช ไม่คืนค่าผ่าน API |
remove_password | (เฉพาะตอนแก้ไข) ส่ง true เพื่อลบรหัสผ่านที่ตั้งไว้ |
ตัวอย่างคำขอ
curl -X PATCH "https://app.isref.cc/api/v1/shortlinks/41" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"title":"Linux course 2026","status":"disabled"}'
import requests
resp = requests.patch(
"https://app.isref.cc/api/v1/shortlinks/41",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
json={
"title": "Linux course 2026",
"status": "disabled",
},
)
print(resp.status_code, resp.json())
<?php
$ch = curl_init("https://app.isref.cc/api/v1/shortlinks/41");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$token}",
"Accept: application/json",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"title" => "Linux course 2026",
"status" => "disabled",
]),
]);
$response = curl_exec($ch);
printf("%d\n%s\n", curl_getinfo($ch, CURLINFO_HTTP_CODE), $response);
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
payload := strings.NewReader(`{"title":"Linux course 2026","status":"disabled"}`)
req, _ := http.NewRequest("PATCH", "https://app.isref.cc/api/v1/shortlinks/41", payload)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}
const resp = await fetch("https://app.isref.cc/api/v1/shortlinks/41", {
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Linux course 2026",
status: "disabled",
}),
});
console.log(resp.status, await resp.json());
200 · "message": "Short link updated."
DELETE /shortlinks/{id}
ลบลิงก์ถาวร
curl -X DELETE "https://app.isref.cc/api/v1/shortlinks/41" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
import requests
resp = requests.delete(
"https://app.isref.cc/api/v1/shortlinks/41",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
)
print(resp.status_code, resp.json())
<?php
$ch = curl_init("https://app.isref.cc/api/v1/shortlinks/41");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$token}",
"Accept: application/json",
],
]);
$response = curl_exec($ch);
printf("%d\n%s\n", curl_getinfo($ch, CURLINFO_HTTP_CODE), $response);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://app.isref.cc/api/v1/shortlinks/41", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}
const resp = await fetch("https://app.isref.cc/api/v1/shortlinks/41", {
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
console.log(resp.status, await resp.json());
204 No Content
ออบเจ็กต์ Shortlink
โครงสร้างที่ทุก endpoint คืนกลับมาในฟิลด์ data (หรือ data.items สำหรับรายการ)
| ฟิลด์ | ข้อกำหนด | คำอธิบาย |
|---|---|---|
id | integer | รหัสตัวเลขของลิงก์ — ใช้อ้างอิงใน URL ของ endpoint |
code | string | โค้ดของลิงก์สั้น |
short_url | string | URL สั้นแบบเต็มพร้อมใช้งาน |
destination_url | string | URL ปลายทาง |
title | string · null | ชื่อลิงก์ (หรือ null) |
description | string · null | คำอธิบาย (หรือ null) |
status | string | active หรือ disabled |
has_password | boolean | true ถ้าลิงก์นี้ตั้งรหัสผ่านไว้ |
available_from | string · null | เวลาเริ่มใช้งาน (ISO-8601 หรือ null) |
is_pending | boolean | true ถ้ายังไม่ถึงเวลา available_from |
expires_at | string · null | เวลาหมดอายุ (ISO-8601 หรือ null) |
is_expired | boolean | true ถ้าเลยเวลาหมดอายุแล้ว |
click_count | integer | จำนวนคลิกสะสม |
last_clicked_at | string · null | เวลาที่ถูกคลิกล่าสุด (หรือ null) |
owner | object · null | เจ้าของลิงก์ { id, name } — แสดงเมื่อโหลดข้อมูลมาด้วย |
created_at | string | เวลาที่สร้าง (ISO-8601) |
updated_at | string | เวลาที่แก้ไขล่าสุด (ISO-8601) |
ช่วงเวลาเปิดใช้งาน
available_from และ expires_at กำหนดช่วงที่ลิงก์ทำงาน มีผลกับการเข้าผ่าน GET /{code} ดังนี้
| สถานะเวลา | ผลลัพธ์เมื่อเปิดลิงก์ |
|---|---|
ก่อน available_from | 404 — หน้า “ยังไม่เปิดใช้งาน” (is_pending: true) |
อยู่ในช่วง | redirect ตามปกติ |
หลัง expires_at | 410 Gone (is_expired: true) |
ส่ง available_from: null (หรือ "") ตอนแก้ไขเพื่อล้างค่า
ลิงก์มีรหัสผ่าน
เมื่อตั้ง password ผู้เข้าชมจะเจอหน้าให้กรอกรหัสก่อน redirect (GET /{code} → ฟอร์ม → POST /{code}) กลไกนี้เป็นส่วนของฝั่งเบราว์เซอร์เท่านั้น API จะรายงานแค่ has_password และไม่คืนค่าแฮชของรหัสผ่าน
ตัวอย่างข้อผิดพลาด
โทเคนไม่มีสิทธิ์
POST /api/v1/shortlinks (token: shorturl:read only) 403 { "success": false, "message": "Invalid ability provided.", "errors": {} }
ข้อมูลไม่ผ่านการตรวจสอบ
POST /api/v1/shortlinks {"destination_url":"javascript:alert(1)"} 422 { "success": false, "message": "The given data was invalid.", "errors": { "destination_url": ["The destination url must start with http:// or https://."] } }
ไม่ใช่เจ้าของลิงก์
GET /api/v1/shortlinks/999 (belongs to another user) 403 { "success": false, "message": "This action is unauthorized.", "errors": {} }