This document outlines the available API endpoints for the React frontend.
Base URL: /api (Relative to the application root)
All requests should include the following headers to ensure correct JSON handling and authentication with Laravel Sanctum:
Accept: application/json (Required to receive JSON responses instead of redirects on errors)Content-Type: application/json (Required when sending JSON data in the request body)Authorization: Bearer <access_token> (Required for protected routes)API requests may return error responses with appropriate HTTP status codes and JSON bodies.
Returned when request data fails validation rules.
{
"message": "The given data was invalid.",
"errors": {
"field_name": [
"The field name field is required."
]
}
}
Returned when authentication fails (e.g., missing or invalid token).
{
"message": "Unauthenticated."
}
Returned when the user is authenticated but does not have permission to perform the action.
{
"message": "This action is unauthorized."
}
Create a new user account.
Endpoint: POST /register
cURL:
curl -X POST http://tuohua-work-api.test/api/register \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "email": "john@tuohuacm.com", "password": "secret_password", "password_confirmation": "secret_password"}'
Request Body:
{
"name": "John Doe",
"email": "john@tuohuacm.com",
"password": "secret_password",
"password_confirmation": "secret_password"
}
name (string, required): User's full name.email (string, required): Valid email address.password (string, required): Min 8 characters.password_confirmation (string, required): Must match password.Response (201 Created):
{
"access_token": "1|laravel_sanctum_token...",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "John Doe",
"email": "john@tuohuacm.com",
"created_at": "...",
"updated_at": "..."
}
}
Error Response (422 Unprocessable Entity): See Error Responses for validation errors (e.g., duplicate email, invalid password, non-tuohuacm.com email).
Authenticate a user.
Endpoint: POST /login
cURL:
curl -X POST http://tuohua-work-api.test/api/login \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"email": "john@tuohuacm.com", "password": "secret_password"}'
Request Body:
{
"email": "john@tuohuacm.com",
"password": "secret_password"
}
email (string, required)password (string, required)Response (200 OK):
{
"access_token": "2|laravel_sanctum_token...",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "John Doe",
"email": "john@tuohuacm.com",
"..."
}
}
Error Response (422 Unprocessable Entity): See Error Responses for invalid credentials.
Invalidate the current session token.
POST /logoutcurl -X POST http://tuohua-work-api.test/api/logout \
-H "Accept: application/json" \
-H "Authorization: Bearer <access_token>"
Authorization: Bearer <access_token>{
"message": "Logged out successfully"
}
Get the DingTalk OAuth login URL.
Endpoint: GET /auth/dingtalk/redirect
cURL:
curl -X GET http://tuohua-work-api.test/api/auth/dingtalk/redirect \
-H "Accept: application/json"
Response (200 OK):
{
"url": "https://login.dingtalk.com/oauth2/auth?redirect_uri=...&response_type=code&client_id=...&scope=openid%20corpid&state=...&prompt=consent",
"state": "random_state_string"
}
url (string): The DingTalk OAuth authorization URL to redirect the user to.state (string): Random state string for CSRF protection.Usage: Frontend should redirect the user to the url. After user authorizes, DingTalk will redirect back to the configured redirect_uri with a code parameter.
Handle the DingTalk OAuth callback and authenticate/register the user.
Endpoint: POST /auth/dingtalk/callback
cURL:
curl -X POST http://tuohua-work-api.test/api/auth/dingtalk/callback \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"code": "authorization_code_from_dingtalk"}'
Request Body:
{
"code": "authorization_code_from_dingtalk"
}
code (string, required): Authorization code returned by DingTalk after user authorization.Response (200 OK):
{
"access_token": "1|laravel_sanctum_token...",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "zhangsan",
"email": "zhangsan@alibaba-inc.com",
"avatar": "https://xxx",
"dingtalk_open_id": "123",
"created_at": "...",
"updated_at": "..."
}
}
Error Response (400 Bad Request): See Error Responses for errors from DingTalk API (e.g., invalid code, failed to get user info).
Error Response (422 Unprocessable Entity): See Error Responses for validation errors (e.g., missing code).
Fetch the currently authenticated user's details.
GET /usercurl -X GET http://tuohua-work-api.test/api/user \
-H "Accept: application/json" \
-H "Authorization: Bearer <access_token>"
Authorization: Bearer <access_token>{
"id": 1,
"name": "John Doe",
"email": "john@tuohuacm.com",
"email_verified_at": null,
"created_at": "2023-10-27T10:00:00.000000Z",
"updated_at": "2023-10-27T10:00:00.000000Z",
"is_admin": false
}
Update the user's name and/or email.
Endpoint: PUT /profile
cURL:
curl -X PUT http://tuohua-work-api.test/api/profile \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{"name": "Jane Doe", "email": "jane@tuohuacm.com"}'
Headers:
Authorization: Bearer <access_token>Request Body:
{
"name": "Jane Doe",
"email": "jane@tuohuacm.com"
}
name (string, optional): User's new full name.email (string, optional): User's new email address.Response (200 OK):
{
"message": "Profile updated successfully.",
"user": {
"id": 1,
"name": "Jane Doe",
"email": "jane@tuohuacm.com",
"..."
}
}
Error Response (401 Unauthorized): See Error Responses if authentication token is missing or invalid.
Error Response (422 Unprocessable Entity): See Error Responses for validation errors (e.g., duplicate email, invalid email format).
Change the user's password.
Endpoint: PUT /profile/password
cURL:
curl -X PUT http://tuohua-work-api.test/api/profile/password \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{"current_password": "old_password", "password": "new_secure_password", "password_confirmation": "new_secure_password"}'
Headers:
Authorization: Bearer <access_token>Request Body:
{
"current_password": "old_password",
"password": "new_secure_password",
"password_confirmation": "new_secure_password"
}
current_password (string, required): Must match the current password.password (string, required): New password (min 8 chars).password_confirmation (string, required): Must match password.Response (200 OK):
{
"message": "Password updated successfully."
}
Error Response (401 Unauthorized): See Error Responses if authentication token is missing or invalid.
Error Response (422 Unprocessable Entity): See Error Responses for validation errors (e.g., current password mismatch, new password not confirmed).
Get a paginated list of all users. Only users with is_admin set to true can access this endpoint.
GET /userscurl -X GET http://tuohua-work-api.test/api/users \
-H "Accept: application/json" \
-H "Authorization: Bearer <access_token>"
Authorization: Bearer <access_token>search (optional): Search users by name or email.page (optional): Page number (default: 1).{
"current_page": 1,
"data": [
{
"id": 1,
"name": "John Doe",
"email": "john@tuohuacm.com",
"is_admin": false,
"created_at": "2023-10-27T10:00:00.000000Z",
"updated_at": "2023-10-27T10:00:00.000000Z"
},
{
"id": 2,
"name": "Jane Smith",
"email": "jane@tuohuacm.com",
"is_admin": true,
"created_at": "2023-10-28T10:00:00.000000Z",
"updated_at": "2023-10-28T10:00:00.000000Z"
}
],
"total": 2,
"per_page": 20,
"last_page": 1
// ... pagination meta
}
Update any user's is_admin attribute. Only users with is_admin set to true can access this endpoint.
Endpoint: PUT /users/{id}/admin-status
cURL:
curl -X PUT http://tuohua-work-api.test/api/users/123/admin-status \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{"is_admin": true}'
Headers:
Authorization: Bearer <access_token>Request Body:
{
"is_admin": true
}
is_admin (boolean, required): The new admin status for the user.Response (200 OK):
{
"id": 123,
"name": "Jane Doe",
"email": "jane@tuohuacm.com",
"is_admin": true,
"created_at": "...",
"updated_at": "..."
}
Error Response (401 Unauthorized): See Error Responses if authentication token is missing or invalid.
Error Response (403 Forbidden): See Error Responses if the current user is not an admin.
Error Response (404 Not Found): If the target user does not exist.
Error Response (422 Unprocessable Entity): See Error Responses for validation errors (e.g., missing or invalid is_admin field).
Upload an image or PDF file (Max 10MB).
POST /uploadAuthorization: Bearer <token>Content-Type: multipart/form-datafile (File, required): The file to upload (jpg, jpeg, png, webp, pdf).{
"path": "uploads/hash.jpg",
"url": "http://tuohua-work-api.test/storage/uploads/hash.jpg"
}
Get a paginated list of the current user's reimbursement reports.
GET /reimbursementsAuthorization: Bearer <token>status (optional): Filter by status (e.g., draft, submitted, approved, rejected).page (optional): Page number (default: 1).{
"current_page": 1,
"data": [
{
"id": 1,
"title": "December 2025 Expenses",
"status": "draft",
"total_amount_cny": "1250.00",
"created_at": "...",
"user": { ... }
}
],
"total": 1
// ... pagination meta
}
Get a paginated list of all users' reimbursement reports (default excludes drafts).
GET /reimbursements/allAuthorization: Bearer <token>user_id (optional): Filter by specific user.status (optional): Filter by status.Create a new reimbursement group (e.g., "Jan 2025").
POST /reimbursements{
"title": "January 2025 Trip"
}
Get details of a specific report, including its items.
GET /reimbursements/{id}{
"id": 1,
"title": "January 2025 Trip",
"items": [
{
"id": 10,
"type": "transport",
"amount_cny": "100.00",
"payment_proofs": ["url1", "url2"]
// ...
}
],
"offset_invoices": ["url_to_pdf"]
}
Update report details (title, offset invoices) while in draft or rejected status.
PUT /reimbursements/{id}{
"title": "Updated Title",
"offset_invoices": ["http://example.com/invoice.pdf"]
}
Submit a draft report for approval.
PUT /reimbursements/{id}/submitsubmitted.Add a specific expense item to a report.
POST /reimbursements/{id}/items{
"type": "transport", // transport, accommodation, general
"date": "2025-12-01",
"currency": "CNY",
"amount_original": 100.50, // Optional, only needed for non-CNY currencies
"amount_cny": 100.50,
"description": "Taxi to airport",
"payment_proofs": ["http://url.to/image.jpg"], // Required array
"invoice_files": ["http://url.to/invoice.pdf"], // Optional array
"meta": { "start": "Home", "end": "Airport" } // Optional generic data
}
Update an existing item.
PUT /reimbursements/{reportId}/items/{itemId}Remove an item from a draft report.
DELETE /reimbursements/{reportId}/items/{itemId}{"message": "Item deleted"}Approve a submitted report.
PUT /reimbursements/{id}/approveapproved, audited_at, and auditor_id.Reject a submitted report.
PUT /reimbursements/{id}/reject{
"reason": "Missing invoices for hotel."
}
rejected.Fetch all records from a specified DingTalk AI Table (Notable). Supports generic filtering.
Endpoint: GET /dingtalk/table
Headers: Authorization: Bearer <token>
cURL:
curl -X GET "http://tuohua-work-api.test/api/dingtalk/table?table_id=TABLE_ID&sheet_id=SHEET_ID&Status=Active" \
-H "Authorization: Bearer <access_token>" \
-H "Accept: application/json"
Query Params:
table_id (string, required): The ID of the DingTalk Table App (Node ID).sheet_id (string, required): The ID of the specific Sheet.[Column Name] (optional): Any other query parameter is treated as a filter for that column name. (e.g., Status=Active, Name=John).Response (200 OK):
{
"success": true,
"data": [
{
"_index": 1,
"Name": "John Doe",
"Status": "Active",
"Phone": "13800138000",
"Created At": "2025-01-01"
},
// ...
]
}
Error Response (422 Unprocessable Entity): Missing table_id or sheet_id.
{
"success": false,
"message": "Validation Error",
"errors": {
"table_id": ["The table id field is required."]
}
}
Error Response (500 Internal Server Error): DingTalk API error or other server issues.
{
"success": false,
"message": "DingTalk API Error (invalidRequest.resource.notFound): The requested resource was not found..."
}