# Logspanel API v1

## Base URL

```text
https://logspanel.com/api/v1
```

## Authentication

Create your API key from your Logspanel account dashboard:

```text
User Dashboard -> Change Password -> API Access -> Generate New API Key
```

Copy the key immediately. It is displayed only once. Generating a new API key revokes the previous key.

Send the key as a bearer token on every protected API request:

```http
Accept: application/json
Authorization: Bearer YOUR_API_KEY_HERE
```

Keep API keys on your server only. Do not expose them in browser JavaScript, mobile apps, screenshots, public repositories, or support tickets.

## Supported Abilities

New user-generated API keys are issued with this project's supported abilities:

```text
wallet:read
logs:read
logs:buy
numbers:read
numbers:buy
boosting:read
boosting:buy
orders:read
```

The Logs, Numbers, and Boosting APIs currently use:

```text
wallet:read
logs:read
logs:buy
numbers:read
numbers:buy
boosting:read
boosting:buy
orders:read
```

## Wallet Balance

Required ability: `wallet:read`

```http
GET /wallet
```

Response:

```json
{
  "success": true,
  "data": {
    "balance": "12500.00",
    "currency": "NGN"
  }
}
```

## Complete Log Catalog

Use the existing `GET /logs/categories` endpoint for the complete Logspanel catalog and continue purchasing through `POST /logs/orders` with `category_id`. Existing positive local IDs, request fields and response fields remain supported. Additional inventory uses stable negative integer category IDs; treat all returned IDs as opaque signed integers and pass them back unchanged. Providers remain internal. No endpoint migration or new API key is required. `/logs/products` remains an optional reference-based representation of the same inventory.

Both catalog representations are paginated: follow `links.next` until it is null. Omit `parent_category_id` when fetching everything. That optional filter matches products assigned to a local parent; unassigned products remain available in the unfiltered catalog. Parent counts include local categories and eligible external products assigned to that parent.

Required ability: `logs:read`.

```http
GET /logs/products?per_page=25&page=1
GET /logs/products/550e8400-e29b-41d4-a716-446655440000
```

`page` must be at least 1. `per_page` defaults to 25 and must be between 1 and 100. Follow `links.next` until it is null to fetch every page. There is no source filter. Invalid pagination values return 422.

A list response contains `success`, `data`, `links` and `meta`. A single-product response contains `success` and one object in `data`. Product example:

```json
{
  "product_ref": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Example Account",
  "description": "Product details and purchase conditions",
  "selling_price": "2500.00",
  "currency": "NGN",
  "available_quantity": 34,
  "min_quantity": 1,
  "max_quantity": 100,
  "purchasable": true
}
```

Treat `product_ref` as an opaque, stable identifier. Use the exact reference returned by the API; do not derive it from a category ID. `selling_price` is the final unit price, including platform markup. Availability reflects the latest saved inventory and is checked again at purchase. Prices and availability may change between browsing and checkout. Unavailable products can disappear from the catalog or return `purchasable: false`; a removed/disabled reference returns `LOG_PRODUCT_NOT_FOUND` (404).

Server-side JavaScript example that fetches all pages:

```javascript
let next = 'https://logspanel.com/api/v1/logs/categories?per_page=100';
const products = [];
while (next) {
  const response = await fetch(next, {
    headers: {
      Accept: 'application/json',
      Authorization: `Bearer ${process.env.LOGSPANEL_API_KEY}`
    }
  });
  if (!response.ok) throw new Error(`Catalog request failed: ${response.status}`);
  const page = await response.json();
  products.push(...page.data);
  next = page.links.next;
}
```

Do not expose the API key in browser code. Cache catalog results in your integration and respect rate limits.

## List Parent Categories

Required ability: `logs:read`

```http
GET /logs/parent-categories
```

Use this legacy endpoint to fetch active parent categories for the category catalog. New integrations should use the complete product catalog above.

Response data item:

```json
{
  "id": 2,
  "name": "Accounts",
  "description": "Social and premium account logs",
  "image": "uploads/parent_categories/accounts.png",
  "available_categories_count": 8
}
```

## List Log Categories

Required ability: `logs:read`

```http
GET /logs/categories?per_page=25&parent_category_id=2
```

`per_page` accepts values from `1` to `100`.
`parent_category_id` is optional and filters log categories by parent category.

Existing positive category IDs remain unchanged. Additional categories have stable negative integer IDs that work with `GET /logs/categories/{category}` and the same `category_id` purchase field. The existing local `price` retains its historical base-price meaning; `selling_price` is the final unit price charged at checkout. For additional inventory, `price` and `selling_price` both contain the final price. `min_quantity`, `max_quantity`, `purchasable` and `product_ref` are additive catalog fields. Images and parent categories can be null. Providers and upstream identifiers are never part of the public category representation.

Response data item:

```json
{
  "id": 12,
  "name": "Facebook Logs",
  "description": "Fresh account logs",
  "image": "https://logspanel.com/image.png",
  "price": "2500.00",
  "currency": "NGN",
  "available_quantity": 34,
  "parent_category": {
    "id": 2,
    "name": "Accounts"
  }
}
```

## Get Log Category

Required ability: `logs:read`

```http
GET /logs/categories/12
```

This endpoint returns category, price, and availability information only. Purchased log details are returned only after a successful purchase.

## Purchase Logs

Required ability: `logs:buy`

```http
POST /logs/orders
```

Request using a reference from the complete catalog:

```json
{
  "product_ref": "550e8400-e29b-41d4-a716-446655440000",
  "quantity": 2,
  "idempotency_key": "website-order-20260922-0001"
}
```

Alternatively, the existing request format remains supported:

```json
{
  "category_id": 12,
  "quantity": 2,
  "idempotency_key": "website-order-20260501-0001"
}
```

The server calculates the unit price, total, and wallet deduction. Do not send a price from your integration.

Send exactly one of `product_ref` or `category_id`, never both. Quantity must be an integer between 1 and 100 and within the product's `min_quantity`/`max_quantity`. `idempotency_key` must be 8–120 characters. Authentication and abilities are unchanged; existing API keys with `logs:read`, `logs:buy` and `orders:read` work with these endpoints.

Use a unique `idempotency_key` for each checkout attempt. If your server retries the same request with the same key, the API returns the original response instead of charging twice.

Successful legacy `category_id` response (201). A `product_ref` purchase returns `product_ref` in place of `product_id`, with the same remaining delivery fields:

```json
{
  "success": true,
  "data": {
    "order_id": "logs-ABC1234567-1777560000",
    "status": "completed",
    "product_id": 12,
    "product_name": "Facebook Logs",
    "quantity": 2,
    "unit_price": "2500.00",
    "total": "5000.00",
    "currency": "NGN",
    "balance_after": "7500.00",
    "items": [
      {
        "serial": 1,
        "details": "log details here",
        "video": null
      }
    ]
  }
}
```

Idempotent retry responses include:

```http
Idempotent-Replayed: true
```

For external-inventory purchases using either `category_id` or `product_ref`, a request that cannot immediately confirm delivery returns HTTP 202:

```json
{
  "success": true,
  "message": "Purchase is pending confirmation. Keep the same idempotency key; do not create another purchase.",
  "data": {
    "order_id": "logs-api-ABC1234567-1777560000",
    "status": "pending",
    "product_ref": "550e8400-e29b-41d4-a716-446655440000",
    "quantity": 2,
    "total": "5000.00",
    "currency": "NGN"
  }
}
```

202 is not a delivery confirmation. Funds are reserved while the result is checked. Retrieve the order below or retry with the **same key and unchanged payload**. Never create a new key to bypass a pending purchase. If it stays pending, contact support with the order ID. A confirmed rejection returns `LOG_PURCHASE_REJECTED` (409), releases reserved funds, and is replayed on subsequent same-key requests. A different payload or endpoint with an already-used key returns `IDEMPOTENCY_KEY_REUSED` (409).

## Get Log Order and Delivered Items

Required ability: `orders:read`.

```http
GET /logs/orders/logs-api-ABC1234567-1777560000
```

Only the order owner can retrieve it. Completed purchases return HTTP 200 with the saved order and `items` (`serial`, `details`, `video`), pending purchases return 202, and confirmed failed purchases return 409. An unknown order or another account's order returns `LOG_ORDER_NOT_FOUND` (404). This lookup never submits another purchase. Historical orders without structured items may provide `delivery_text` instead. Order history retains `product_id` (the public category ID) and adds `product_ref` when available.

Unexpected purchase/database failures return `LOG_SERVICE_UNAVAILABLE` (503) with a support `request_id`. Keep the original idempotency key when retrying. This response does not prove an account restriction or insufficient funds; those have separate error codes. Support can correlate the request ID with safe server diagnostics without exposing credentials or database queries. Existing keys remain valid, but disabled/banned accounts receive 403 across protected v1 endpoints.

Treat delivered items as confidential. Poll pending orders conservatively (for example every 10 seconds) and stop after a terminal result. The shared API limit is 60 requests per minute; purchases also have a 20-per-minute limit. On 429, respect `Retry-After`. Coordinate polling across orders to stay within the shared limit.

Additional log errors: `LOG_PRODUCT_NOT_FOUND` (404), `LOG_QUANTITY_OUT_OF_RANGE` (422), `LOG_SERVICE_UNAVAILABLE` (503), `LOG_PURCHASE_PENDING` (409), and `LOG_PURCHASE_FAILED` (422). Validation errors return 422 with an `errors` object. Missing token abilities return 403.

## List Log Orders

Required ability: `orders:read`

```http
GET /logs/orders?per_page=25
```

Response data item:

```json
{
  "order_id": "logs-ABC1234567-1777560000",
  "status": "completed",
  "product_id": 12,
  "product_name": "Facebook Logs",
  "quantity": 2,
  "unit_price": "2500.00",
  "total": "5000.00",
  "currency": "NGN",
  "created_at": "2026-05-01T12:00:00.000000Z"
}
```

## List Number Countries

Required ability: `numbers:read`

```http
GET /numbers/countries
```

Use this endpoint to fetch countries available for phone number activations.

Response data item:

```json
{
  "id": 1,
  "name": "United States",
  "short": "us",
  "calling_code": "1",
  "region": "North America",
  "image": "https://flagcdn.com/20x15/us.png"
}
```

## List Number Services

Required ability: `numbers:read`

```http
GET /numbers/services?country_id=1
```

The server calculates final wallet prices. Do not calculate customer-facing prices from raw catalog data.

Response data item:

```json
{
  "service_id": "123",
  "service_name": "Telegram",
  "country_id": "1",
  "country_name": "United States",
  "price": "850.00",
  "currency": "NGN",
  "available_quantity": 92,
  "category": "Activation"
}
```

## List Number Area Codes

Required ability: `numbers:read`

```http
GET /numbers/area-codes?country_id=1&service_id=123
```

Area codes are optional and only apply to services/countries that support area filtering. If no specific area codes are available, the API returns `Random` so the integration can purchase any available number.

Response:

```json
{
  "success": true,
  "data": {
    "codes": ["Random"]
  }
}
```

## Purchase Number

Required ability: `numbers:buy`

```http
POST /numbers/orders
```

Request:

```json
{
  "country_id": 1,
  "service_id": 123,
  "selected_area_codes": ["917"],
  "idempotency_key": "number-order-20260501-0001"
}
```

Logspanel calculates the price, reserves the number, deducts the wallet balance, and returns a stable project-owned response.

Successful response:

```json
{
  "success": true,
  "data": {
    "order_id": "123456789",
    "status": "waiting_for_code",
    "number": "+1234567890",
    "service_id": "123",
    "service_name": "Telegram",
    "country_id": "1",
    "country_name": "United States",
    "price": "850.00",
    "currency": "NGN",
    "time_left": 1200,
    "balance_after": "9150.00"
  }
}
```

Idempotent retry responses include:

```http
Idempotent-Replayed: true
```

## List Number Orders

Required ability: `orders:read`

```http
GET /numbers/orders?per_page=25
```

Response data item:

```json
{
  "order_id": "123456789",
  "status": "waiting_for_code",
  "number": "+1234567890",
  "service_id": "123",
  "service_name": "Telegram",
  "country_id": "1",
  "country_name": "United States",
  "price": "850.00",
  "currency": "NGN",
  "code": null,
  "message": "Still waiting...",
  "time_left": 1200,
  "refunded": false,
  "created_at": "2026-05-01T12:00:00.000000Z"
}
```

## Get Number Order

Required ability: `orders:read`

```http
GET /numbers/orders/123456789
```

Retrieves the stored details of a single number order owned by the authenticated account. Use this endpoint for order lookups, audit views, and displaying the most recently saved status without running a live code check.

Response:

```json
{
  "success": true,
  "data": {
    "order_id": "123456789",
    "status": "waiting_for_code",
    "number": "+1234567890",
    "service_id": "123",
    "service_name": "Telegram",
    "country_id": "1",
    "country_name": "United States",
    "price": "850.00",
    "currency": "NGN",
    "code": null,
    "message": "Still waiting...",
    "time_left": 1200,
    "refunded": false,
    "created_at": "2026-05-01T12:00:00.000000Z"
  }
}
```

## Check Number Code

Required ability: `orders:read`

```http
POST /numbers/orders/123456789/check
```

Runs a live code check for the number order and returns the latest status. In production, poll this endpoint every 5-10 seconds after purchase until `code` contains the received SMS verification code.

Response:

```json
{
  "success": true,
  "data": {
    "order_id": "123456789",
    "status": "completed",
    "number": "+1234567890",
    "service_id": "123",
    "service_name": "Telegram",
    "country_id": "1",
    "country_name": "United States",
    "price": "850.00",
    "currency": "NGN",
    "code": "12345",
    "message": "Code Received",
    "time_left": 875,
    "refunded": false,
    "created_at": "2026-05-01T12:00:00.000000Z"
  }
}
```

## Cancel Number

Required ability: `numbers:buy`

```http
POST /numbers/orders/123456789/cancel
```

Cancels and refunds an eligible number order when no SMS code has arrived.

## List Boost Categories

Required ability: `boosting:read`

```http
GET /boost/categories
```

Returns unique boost categories for service filtering.

Response:

```json
{
  "success": true,
  "data": [
    { "name": "Instagram Followers" },
    { "name": "TikTok Views" }
  ]
}
```

## List Boost Services

Required ability: `boosting:read`

```http
GET /boost/services?category=Instagram%20Followers
```

The API fetches available boost services and returns final NGN wallet prices. `category` is optional.

Response data item:

```json
{
  "service_id": "101",
  "service_name": "Instagram Followers",
  "category": "Instagram Followers",
  "type": "Default",
  "rate_per_1000": "1500.00",
  "usd_rate_per_1000": "1.0000",
  "currency": "NGN",
  "min": 100,
  "max": 10000,
  "refill": false,
  "cancel": false
}
```

## Get Boost Service

Required ability: `boosting:read`

```http
GET /boost/services/101
```

Use this endpoint before checkout to determine the service type, min/max quantity, cancellation support, and required request fields.

## Purchase Boost

Required ability: `boosting:buy`

```http
POST /boost/orders
```

Request for a `Default` service:

```json
{
  "service_id": "101",
  "link": "https://instagram.com/p/example",
  "quantity": 1000,
  "idempotency_key": "boost-order-20260502-0001"
}
```

Logspanel calculates the price, creates the boost order, deducts the wallet balance, and stores the order locally. Do not send price from your integration.

Successful response:

```json
{
  "success": true,
  "data": {
    "order_id": "987654321",
    "status": "pending",
    "service_id": "101",
    "service_name": "Instagram Followers",
    "category": "Instagram Followers",
    "type": "Default",
    "link": "https://instagram.com/p/example",
    "quantity": 1000,
    "price": "1500.00",
    "currency": "NGN",
    "cancel": true,
    "balance_after": "8500.00"
  }
}
```

Idempotent retry responses include:

```http
Idempotent-Replayed: true
```

## List Boost Orders

Required ability: `orders:read`

```http
GET /boost/orders?per_page=25
```

Response data item:

```json
{
  "order_id": "987654321",
  "status": "processing",
  "service_id": "101",
  "service_name": "Instagram Followers",
  "link": "https://instagram.com/p/example",
  "quantity": 1000,
  "price": "1500.00",
  "currency": "NGN",
  "start_count": 120,
  "remains": 350,
  "refunded": false,
  "cancel": true,
  "created_at": "2026-05-02T12:00:00.000000Z"
}
```

## Get Boost Order

Required ability: `orders:read`

```http
GET /boost/orders/987654321
```

Retrieves the latest locally stored boost order owned by the authenticated account.

## Check Boost Status

Required ability: `orders:read`

```http
POST /boost/orders/987654321/check
```

Runs a live status refresh for the boost order and returns the updated local record. If the order is cancelled, the refund-safe processor marks it refunded and credits the user's wallet once.

## Cancel Boost Order

Required ability: `boosting:buy`

```http
POST /boost/orders/987654321/cancel
```

Submits a cancellation request for an eligible boost order. Refunds are completed only after the order status confirms as cancelled.

Successful response:

```json
{
  "success": true,
  "message": "Cancellation request submitted.",
  "data": {
    "order_id": "987654321",
    "status": "canceling",
    "service_id": "101",
    "service_name": "Instagram Followers",
    "link": "https://instagram.com/p/example",
    "quantity": 1000,
    "price": "1500.00",
    "currency": "NGN",
    "start_count": null,
    "remains": null,
    "refunded": false,
    "cancel": false,
    "created_at": "2026-05-02T12:00:00.000000Z"
  }
}
```

## Error Format

```json
{
  "success": false,
  "status": 402,
  "message": "Insufficient wallet balance.",
  "code": "INSUFFICIENT_BALANCE"
}
```

Common error codes:

```text
USER_DISABLED
USER_BANNED
UNAUTHENTICATED
VALIDATION_FAILED
METHOD_NOT_ALLOWED
LOG_CATEGORY_NOT_FOUND
INSUFFICIENT_BALANCE
OUT_OF_STOCK
NUMBER_SERVICE_NOT_FOUND
NUMBER_ORDER_NOT_FOUND
NUMBER_OUT_OF_STOCK
NUMBER_PRICE_CHANGED
NUMBER_CODE_ALREADY_RECEIVED
NUMBER_REFUND_FAILED
BOOST_SERVICE_NOT_FOUND
BOOST_ORDER_NOT_FOUND
BOOST_CANCEL_UNAVAILABLE
BOOST_CANCEL_REJECTED
BOOST_QUANTITY_REQUIRED
BOOST_QUANTITY_OUT_OF_RANGE
BOOST_LINK_REQUIRED
TOOL_NOT_FOUND
TOOL_ORDER_NOT_FOUND
TOOL_ALREADY_OWNED
TOOL_DOWNLOAD_NOT_AVAILABLE
TOOL_DOWNLOAD_FAILED
IDEMPOTENCY_KEY_REUSED
```

Missing, invalid, or revoked API keys return:

```json
{
  "success": false,
  "status": 401,
  "message": "Unauthenticated. Please provide a valid bearer token.",
  "code": "UNAUTHENTICATED"
}
```

Generate a new API key from your account dashboard if a key is lost or compromised.

## cURL Example

```bash
curl -X POST "https://your-domain.com/api/v1/logs/orders" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"category_id":12,"quantity":1,"idempotency_key":"website-order-20260501-0001"}'
```
