MENU navbar-image

Introduction

Permbot REST surface for authentication, delegated client provisioning, tracked websites, and mock crawl ingestion.

This documentation aims to provide all the information you need to work with our API.

<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Authenticate with Laravel Sanctum personal access tokens: call POST /api/login or POST /api/register, then paste the token into the Bearer field.

Account

Profile settings and two-factor authentication for the authenticated user.

Update display name

requires authentication

Example request:
curl --request PUT \
    "https://dev.permbot.net/api/me/name" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Acme Plumbing\"
}"
const url = new URL(
    "https://dev.permbot.net/api/me/name"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Acme Plumbing"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{"message":"...","user":{...}}
 

Request      

PUT api/me/name

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Example: Acme Plumbing

Update password

requires authentication

Example request:
curl --request PUT \
    "https://dev.permbot.net/api/me/password" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"current_password\": \"architecto\",
    \"password\": \"|]|{+-\",
    \"password_confirmation\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/me/password"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "current_password": "architecto",
    "password": "|]|{+-",
    "password_confirmation": "architecto"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "..."
}
 

Example response (422, invalid password):


{
    "message": "..."
}
 

Request      

PUT api/me/password

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

current_password   string     

Example: architecto

password   string     

Example: |]|{+-

password_confirmation   string     

Example: architecto

Update email

requires authentication

When the session was opened with two-factor authentication, the new email is applied immediately. Otherwise a confirmation link is sent to the current email address.

Example request:
curl --request PUT \
    "https://dev.permbot.net/api/me/email" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"owner@newdomain.com\",
    \"current_password\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/me/email"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "owner@newdomain.com",
    "current_password": "architecto"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, direct):


{"message":"...","pending_confirmation":false,"user":{...}}
 

Example response (200, pending):


{"message":"...","pending_confirmation":true,"user":{...}}
 

Request      

PUT api/me/email

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Example: owner@newdomain.com

current_password   string     

Example: architecto

Cancel a pending email change

requires authentication

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/me/email/pending" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/me/email/pending"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/me/email/pending

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Two-factor authentication status

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/me/two-factor" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/me/two-factor"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/me/two-factor

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Prepare two-factor setup

requires authentication

Returns a TOTP secret and otpauth URL for an authenticator app. Call enable with a valid code to finish.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/me/two-factor/prepare" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/me/two-factor/prepare"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/me/two-factor/prepare

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Enable two-factor authentication

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/me/two-factor/enable" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"123456\"
}"
const url = new URL(
    "https://dev.permbot.net/api/me/two-factor/enable"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "123456"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "...",
    "recovery_codes": [
        "XXXX-XXXX",
        "..."
    ]
}
 

Request      

POST api/me/two-factor/enable

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

code   string     

Six-digit authenticator code. Example: 123456

Disable two-factor authentication

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/me/two-factor/disable" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"password\": \"|]|{+-\",
    \"code\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/me/two-factor/disable"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "password": "|]|{+-",
    "code": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/me/two-factor/disable

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

password   string     

Example: |]|{+-

code   string     

Six-digit authenticator code. Example: architecto

Activity logs

Append-only audit trail of panel actions with masked IP addresses and actor attribution. Agency accounts can list every event scoped to their reseller lineage; all users can list their own actions.

List activity logs for the authenticated user

requires authentication

Returns events where the authenticated user is the actor.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/activity-logs?type=site_scan_completed&per_page=20" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/activity-logs"
);

const params = {
    "type": "site_scan_completed",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": 1,
            "type": "site_created",
            "description": "Site plumbing.example was created.",
            "target": "plumbing.example",
            "variant": "neutral",
            "actor": {
                "id": 1,
                "name": "Agency User",
                "email": "agency@example.com"
            },
            "ip_address": "203.0.113.0",
            "context": {
                "site_id": 1,
                "domain_name": "plumbing.example"
            },
            "created_at": "2026-05-19T12:00:00.000000Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
 

Request      

GET api/activity-logs

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

type   string  optional    

Filter by activity type. Example: site_scan_completed

per_page   integer  optional    

Items per page (max 50). Example: 20

List activity logs for the authenticated agency

requires authentication

Returns every event scoped to the agency account, including actions performed by child client users and background jobs tied to sites owned by the agency or its clients.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/agency/activity-logs?type=site_created&per_page=20" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/agency/activity-logs"
);

const params = {
    "type": "site_created",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": 1,
            "type": "site_created",
            "description": "Site plumbing.example was created.",
            "target": "plumbing.example",
            "variant": "neutral",
            "actor": {
                "id": 2,
                "name": "Client User",
                "email": "client@example.com"
            },
            "ip_address": "203.0.113.0",
            "context": {
                "site_id": 1,
                "domain_name": "plumbing.example"
            },
            "created_at": "2026-05-19T12:00:00.000000Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
 

Request      

GET api/agency/activity-logs

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

type   string  optional    

Filter by activity type. Example: site_created

per_page   integer  optional    

Items per page (max 50). Example: 20

Admin billing

PATCH api/admin/users/{userId}/subscription

requires authentication

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/admin/users/architecto/subscription" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"package_id\": 16,
    \"starts_at\": \"2026-06-03T11:52:30\",
    \"ends_at\": \"2052-06-26\"
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/users/architecto/subscription"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "package_id": 16,
    "starts_at": "2026-06-03T11:52:30",
    "ends_at": "2052-06-26"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/admin/users/{userId}/subscription

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

userId   string     

Example: architecto

Body Parameters

package_id   integer     

The id of an existing record in the packages table. Example: 16

starts_at   string  optional    

Must be a valid date. Example: 2026-06-03T11:52:30

ends_at   string  optional    

Must be a valid date. Must be a date after starts_at. Example: 2052-06-26

Admin billing — invoices

GET api/admin/invoices

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/invoices" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"architecto\",
    \"user_id\": 16,
    \"per_page\": 22
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/invoices"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "architecto",
    "user_id": 16,
    "per_page": 22
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/admin/invoices

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

status   string  optional    

Example: architecto

user_id   integer  optional    

The id of an existing record in the users table. Example: 16

per_page   integer  optional    

Must be at least 1. Must not be greater than 100. Example: 22

POST api/admin/invoices/{invoiceId}/approve

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/invoices/architecto/approve" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/invoices/architecto/approve"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/admin/invoices/{invoiceId}/approve

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

invoiceId   string     

Example: architecto

POST api/admin/invoices/{invoiceId}/reject

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/invoices/architecto/reject" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"payment_details\": \"b\"
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/invoices/architecto/reject"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "payment_details": "b"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/admin/invoices/{invoiceId}/reject

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

invoiceId   string     

Example: architecto

Body Parameters

payment_details   string  optional    

Must not be greater than 5000 characters. Example: b

Admin billing — packages

GET api/admin/packages/feature-options

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/packages/feature-options" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/packages/feature-options"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/admin/packages/feature-options

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/admin/packages

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/packages" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/packages"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/admin/packages

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/admin/packages

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/packages" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/packages"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/admin/packages

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

PATCH api/admin/packages/{packageId}

requires authentication

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/admin/packages/architecto" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/packages/architecto"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "PATCH",
    headers,
}).then(response => response.json());

Request      

PATCH api/admin/packages/{packageId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

packageId   string     

Example: architecto

DELETE api/admin/packages/{packageId}

requires authentication

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/admin/packages/architecto" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/packages/architecto"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/admin/packages/{packageId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

packageId   string     

Example: architecto

Admin support tickets

Admin group operators can manage all support requests.

GET api/admin/support/tickets

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/support/tickets" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"per_page\": 1,
    \"status\": \"closed\",
    \"has_unread_requester_reply\": false
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/support/tickets"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 1,
    "status": "closed",
    "has_unread_requester_reply": false
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/admin/support/tickets

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

per_page   integer  optional    

Must be at least 1. Must not be greater than 50. Example: 1

status   string  optional    

Example: closed

Must be one of:
  • open
  • replied
  • closed
has_unread_requester_reply   boolean  optional    

Example: false

GET api/admin/support/tickets/{ticketUid}

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/admin/support/tickets/{ticketUid}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ticketUid   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

POST api/admin/support/tickets/{ticketUid}/messages

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/messages" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"message\": \"b\"
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/messages"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "message": "b"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/admin/support/tickets/{ticketUid}/messages

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ticketUid   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

Body Parameters

message   string     

Must not be greater than 10000 characters. Example: b

PATCH api/admin/support/tickets/{ticketUid}

requires authentication

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/admin/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"open\"
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "open"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/admin/support/tickets/{ticketUid}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ticketUid   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

Body Parameters

status   string     

Example: open

Must be one of:
  • open
  • replied
  • closed

Admin users

Admin group operators can list and manage agency and client accounts. Provisioning is CLI-only.

List all agency and client accounts

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/users?type=agency&name%5Blike%5D=Plumbing&email%5Beq%5D=owner%40example.com&email%5Blike%5D=%40example.com&suspended=&parent_id=1&sort=name&order=asc" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/users"
);

const params = {
    "type": "agency",
    "name[like]": "Plumbing",
    "email[eq]": "owner@example.com",
    "email[like]": "@example.com",
    "suspended": "0",
    "parent_id": "1",
    "sort": "name",
    "order": "asc",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": 1,
            "name": "Agency User",
            "email": "agency@example.com",
            "type": "agency",
            "parent_id": null,
            "suspended_at": null,
            "sites_count": 2,
            "agency_profile": {
                "brand_name": "WhiteLabel Co",
                "subdomain": "acme"
            },
            "parent": null,
            "created_at": "2026-05-19T10:00:00.000000Z",
            "updated_at": "2026-05-19T10:00:00.000000Z"
        }
    ]
}
 

Request      

GET api/admin/users

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

type   string  optional    

Filter by agency or client. Example: agency

name[like]   string  optional    

Filter by name (case-insensitive). Example: Plumbing

email[eq]   string  optional    

Filter by exact email. Example: owner@example.com

email[like]   string  optional    

Filter by email substring. Example: @example.com

suspended   boolean  optional    

When true, only suspended users; when false, only active. Example: false

parent_id   integer  optional    

Filter clients by managing agency user id. Example: 1

sort   string  optional    

Sort column. Default: id. Example: name

order   string  optional    

asc or desc. Default: desc. Example: asc

Show a single agency or client account summary

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/users/12" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/users/12"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "id": 12,
        "name": "Plumbing Co LLC",
        "email": "owner@example.com",
        "type": "client",
        "parent_id": 1,
        "suspended_at": null,
        "sites_count": 2,
        "agency_profile": null,
        "parent": {
            "id": 1,
            "name": "Agency User",
            "email": "agency@example.com",
            "type": "agency"
        },
        "created_at": "2026-05-19T10:00:00.000000Z",
        "updated_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

GET api/admin/users/{userId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

userId   integer     

Target user primary key. Example: 12

Update an agency or client account

requires authentication

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/admin/users/12" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Plumbing Co LLC\",
    \"email\": \"owner@example.com\",
    \"password\": \"changeMe!234\",
    \"password_confirmation\": \"architecto\",
    \"brand_name\": \"WhiteLabel Co\",
    \"subdomain\": \"acme\"
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/users/12"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Plumbing Co LLC",
    "email": "owner@example.com",
    "password": "changeMe!234",
    "password_confirmation": "architecto",
    "brand_name": "WhiteLabel Co",
    "subdomain": "acme"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "user": {
        "id": 12,
        "name": "Plumbing Co LLC",
        "email": "owner@example.com",
        "type": "client",
        "parent_id": 1,
        "suspended_at": null,
        "sites_count": 2,
        "agency_profile": null,
        "parent": {
            "id": 1,
            "name": "Agency User",
            "email": "agency@example.com",
            "type": "agency"
        },
        "created_at": "2026-05-19T10:00:00.000000Z",
        "updated_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

PATCH api/admin/users/{userId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

userId   integer     

Target user primary key. Example: 12

Body Parameters

name   string  optional    

optional Example: Plumbing Co LLC

email   string  optional    

optional Example: owner@example.com

password   string  optional    

optional Example: changeMe!234

password_confirmation   string     

when password is present. Example: architecto

brand_name   string  optional    

optional Agency whitelabel brand name. Example: WhiteLabel Co

subdomain   string  optional    

optional Agency subdomain slug. Example: acme

Suspend an agency or client account

requires authentication

Revokes all Sanctum tokens for the target user.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/users/12/suspend" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/users/12/suspend"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "user": {
        "id": 12,
        "suspended_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

POST api/admin/users/{userId}/suspend

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

userId   integer     

Target user primary key. Example: 12

Unsuspend an agency or client account

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/users/12/unsuspend" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/users/12/unsuspend"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "user": {
        "id": 12,
        "suspended_at": null
    }
}
 

Request      

POST api/admin/users/{userId}/unsuspend

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

userId   integer     

Target user primary key. Example: 12

Agency clients

Agencies can create client child users scoped with parent_id.

List client child users for the authenticated agency

requires authentication

Filters and sort may be combined in a single request.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/agency/clients?name%5Blike%5D=Plumbing&email%5Beq%5D=client-plumbing%40example.com&email%5Blike%5D=plumbing%40&sort=name&order=asc" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/agency/clients"
);

const params = {
    "name[like]": "Plumbing",
    "email[eq]": "client-plumbing@example.com",
    "email[like]": "plumbing@",
    "sort": "name",
    "order": "asc",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": 12,
            "name": "Plumbing Co LLC",
            "email": "client-plumbing@example.com",
            "type": "client",
            "parent_id": 1,
            "sites_count": 2,
            "created_at": "2026-05-19T10:00:00.000000Z",
            "updated_at": "2026-05-19T10:00:00.000000Z"
        }
    ]
}
 

Request      

GET api/agency/clients

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

name[like]   string  optional    

Filter clients whose name contains this value (case-insensitive). Example: Plumbing

email[eq]   string  optional    

Filter clients whose email equals this value. Example: client-plumbing@example.com

email[like]   string  optional    

Filter clients whose email contains this value (case-insensitive). Cannot be sent together with email[eq]. Example: plumbing@

sort   string  optional    

Sort column: id, name, email, created_at, updated_at, sites_count. Default: id. Example: name

order   string  optional    

Sort direction: asc or desc. Default: desc. Example: asc

Create a client child user for the authenticated agency

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/agency/clients" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Plumbing Co LLC\",
    \"email\": \"client-plumbing@example.com\",
    \"credential_mode\": \"manual\",
    \"password\": \"changeMe!234\",
    \"password_confirmation\": \"architecto\",
    \"include_password_in_email\": false
}"
const url = new URL(
    "https://dev.permbot.net/api/agency/clients"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Plumbing Co LLC",
    "email": "client-plumbing@example.com",
    "credential_mode": "manual",
    "password": "changeMe!234",
    "password_confirmation": "architecto",
    "include_password_in_email": false
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201, success):


{
    "client": {
        "id": 12,
        "name": "Plumbing Co LLC",
        "email": "client-plumbing@example.com",
        "type": "client",
        "parent_id": 1,
        "sites_count": 0,
        "created_at": "2026-05-19T10:00:00.000000Z",
        "updated_at": "2026-05-19T10:00:00.000000Z"
    }
}
 

Request      

POST api/agency/clients

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Example: Plumbing Co LLC

email   string     

Unique email among all users. Example: client-plumbing@example.com

credential_mode   string  optional    

manual (default) — agency sets the password; set_password_email — client receives a link to choose a password. Example: manual

password   string  optional    

Required when credential_mode is manual. Example: changeMe!234

password_confirmation   string  optional    

Required when password is present. Must match password. Example: architecto

include_password_in_email   boolean  optional    

When credential_mode is manual, optionally include the plain password in the welcome email. Default: false. Example: false

Update a managed client child user

requires authentication

Send only keys you want to change.

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/agency/clients/12" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Plumbing Co LLC\",
    \"email\": \"client-plumbing@example.com\",
    \"password\": \"changeMe!234\",
    \"password_confirmation\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/agency/clients/12"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Plumbing Co LLC",
    "email": "client-plumbing@example.com",
    "password": "changeMe!234",
    "password_confirmation": "architecto"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "client": {
        "id": 12,
        "name": "Plumbing Co LLC",
        "email": "client-plumbing@example.com",
        "type": "client",
        "parent_id": 1,
        "sites_count": 2,
        "created_at": "2026-05-19T10:00:00.000000Z",
        "updated_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

PATCH api/agency/clients/{clientId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

clientId   integer     

Client user primary key. Example: 12

Body Parameters

name   string  optional    

optional Example: Plumbing Co LLC

email   string  optional    

optional Unique email among all users. Example: client-plumbing@example.com

password   string  optional    

optional Example: changeMe!234

password_confirmation   string     

when password is present. Must match password. Example: architecto

Delete a managed client child user

requires authentication

Clients that still own one or more sites cannot be deleted until those sites are removed or reassigned.

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/agency/clients/12" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/agency/clients/12"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204, success):

Empty response
 

Example response (409, has sites):


{
    "message": "This client still owns one or more sites. Remove or reassign those sites before deleting the client."
}
 

Request      

DELETE api/agency/clients/{clientId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

clientId   integer     

Client user primary key. Example: 12

Authentication

Tokens are issued via Laravel Sanctum personal access tokens.

Register

Creates agency (always seeds an AgencyProfile row you can customise later) or a standalone client account with parent_id = null.

Standalone accounts can authenticate and manage POST /api/sites like agencies, unless a route intentionally restricts access to reseller personas.

Legacy callers may omit account_type entirely; absent values default server-side to agency.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/register" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"account_type\": \"client\",
    \"name\": \"Acme Plumbing\",
    \"email\": \"owner@example.com\",
    \"password\": \"changeMe!234\",
    \"brand_name\": \"WhiteLabel Co.\",
    \"subdomain\": \"acme\",
    \"locale\": \"tr\",
    \"password_confirmation\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/register"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "account_type": "client",
    "name": "Acme Plumbing",
    "email": "owner@example.com",
    "password": "changeMe!234",
    "brand_name": "WhiteLabel Co.",
    "subdomain": "acme",
    "locale": "tr",
    "password_confirmation": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201, success):


{
    "token": "1|plainTextToken",
    "token_type": "Bearer",
    "user": {
        "id": 1,
        "name": "Acme Plumbing",
        "email": "owner@example.com",
        "type": "agency"
    }
}
 

Example response (422, validation):


{
    "message": "...",
    "errors": {}
}
 

Request      

POST api/register

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

account_type   string  optional    

agency registers a reseller console user. client registers a standalone tenant. Omit to default to agency (existing integrations). Example: client

name   string     

Display name. Example: Acme Plumbing

email   string     

Unique email among all rows in users. Example: owner@example.com

password   string     

Minimum complexity enforced by Laravel. Example: changeMe!234

brand_name   string  optional    

Accepted only when account_type resolves to agency. Example: WhiteLabel Co.

subdomain   string  optional    

Accepted only for agency. Nullable slug unique per agency_profiles. Example: acme

locale   string  optional    

Optional panel language (en or tr). Defaults to en. Example: tr

password_confirmation   string     

Must match password. Example: architecto

Login

Validates credentials and returns a Bearer token. The type reflects the authenticated user (admin, admin_group, agency, or client).

Example request:
curl --request POST \
    "https://dev.permbot.net/api/login" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"agency@example.com\",
    \"password\": \"changeMe!234\"
}"
const url = new URL(
    "https://dev.permbot.net/api/login"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "agency@example.com",
    "password": "changeMe!234"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "token": "1|plainTextToken",
    "token_type": "Bearer",
    "user": {
        "id": 1,
        "name": "Agency User",
        "email": "agency@example.com",
        "type": "agency"
    }
}
 

Example response (401, invalid credentials):


{
    "message": "..."
}
 

Request      

POST api/login

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Example: agency@example.com

password   string     

Example: changeMe!234

Complete login with two-factor authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/login/two-factor" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"two_factor_token\": \"architecto\",
    \"code\": \"123456\",
    \"recovery_code\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/login/two-factor"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "two_factor_token": "architecto",
    "code": "123456",
    "recovery_code": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{"token":"1|plainTextToken","token_type":"Bearer","user":{...}}
 

Example response (422, invalid code):


{
    "message": "..."
}
 

Request      

POST api/login/two-factor

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

two_factor_token   string     

Token returned by login when requires_two_factor is true. Example: architecto

code   string     

Six-digit authenticator code. Example: 123456

recovery_code   string  optional    

optional One-time recovery code instead of code. Example: architecto

Forgot password

Sends a password reset link when an active account exists for the given email. The response is always the same to avoid email enumeration.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/forgot-password" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"agency@example.com\"
}"
const url = new URL(
    "https://dev.permbot.net/api/forgot-password"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "agency@example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "..."
}
 

Request      

POST api/forgot-password

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Example: agency@example.com

Reset password

Completes a password reset using the token from the email link. All existing API tokens for the user are revoked.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/reset-password" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"agency@example.com\",
    \"token\": \"abc123...\",
    \"password\": \"changeMe!234\",
    \"password_confirmation\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/reset-password"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "agency@example.com",
    "token": "abc123...",
    "password": "changeMe!234",
    "password_confirmation": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "..."
}
 

Example response (422, invalid token):


{
    "message": "..."
}
 

Request      

POST api/reset-password

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Example: agency@example.com

token   string     

Token from the reset email. Example: abc123...

password   string     

Example: changeMe!234

password_confirmation   string     

Must match password. Example: architecto

Confirm pending email change

Example request:
curl --request POST \
    "https://dev.permbot.net/api/confirm-email-change" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"owner@example.com\",
    \"token\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/confirm-email-change"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "owner@example.com",
    "token": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "message": "..."
}
 

Example response (422, invalid token):


{
    "message": "..."
}
 

Request      

POST api/confirm-email-change

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

Current email address. Example: owner@example.com

token   string     

Token from the confirmation email. Example: architecto

Logout

requires authentication

Revokes the Sanctum personal access token used on this request so it cannot be reused. Other sessions (tokens issued on other devices or earlier logins) stay valid unless revoked separately.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/logout" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/logout"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "..."
}
 

Request      

POST api/logout

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Current authenticated user (refresh session after role changes).

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/me" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/me"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/me

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Update panel locale

requires authentication

Persists the user's preferred panel language for API copy and transactional emails.

Example request:
curl --request PUT \
    "https://dev.permbot.net/api/me/locale" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"locale\": \"tr\"
}"
const url = new URL(
    "https://dev.permbot.net/api/me/locale"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "locale": "tr"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{"message":"...","user":{"id":1,"locale":"tr",...}}
 

Request      

PUT api/me/locale

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

locale   string     

en or tr. Example: tr

Billing

GET api/billing/status

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/status" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/status"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/billing/status

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/billing/packages

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/packages" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/packages"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/billing/packages

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/billing/payment-methods

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/payment-methods" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/payment-methods"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/billing/payment-methods

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/billing/subscription

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/subscription" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/subscription"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/billing/subscription

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/billing/invoices

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/invoices" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/invoices"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/billing/invoices

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/billing/billing-profile

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/billing-profile" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/billing-profile"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/billing/billing-profile

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

PUT api/billing/billing-profile

requires authentication

Example request:
curl --request PUT \
    "https://dev.permbot.net/api/billing/billing-profile" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/billing-profile"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "PUT",
    headers,
}).then(response => response.json());

Request      

PUT api/billing/billing-profile

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/billing/upgrade

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/billing/upgrade" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"package_id\": 16,
    \"payment_method\": \"architecto\",
    \"payment_details\": \"n\"
}"
const url = new URL(
    "https://dev.permbot.net/api/billing/upgrade"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "package_id": 16,
    "payment_method": "architecto",
    "payment_details": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/billing/upgrade

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

package_id   integer     

The id of an existing record in the packages table. Example: 16

payment_method   string  optional    

Example: architecto

payment_details   string  optional    

Must not be greater than 5000 characters. Example: n

billing_profile   object  optional    

POST api/billing/upgrade/cancel

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/billing/upgrade/cancel" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/upgrade/cancel"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/billing/upgrade/cancel

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Billing — iyzico

GET api/billing/iyzico/config

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/iyzico/config" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/iyzico/config"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/billing/iyzico/config

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/billing/invoices/{invoiceId}/iyzico/checkout

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/billing/invoices/architecto/iyzico/checkout" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/invoices/architecto/iyzico/checkout"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/billing/invoices/{invoiceId}/iyzico/checkout

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

invoiceId   string     

Example: architecto

POST api/billing/iyzico/complete

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/billing/iyzico/complete" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"b\"
}"
const url = new URL(
    "https://dev.permbot.net/api/billing/iyzico/complete"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "b"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/billing/iyzico/complete

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

Must not be greater than 512 characters. Example: b

Blog (public)

Marketing site blog content. No authentication required; only published posts are exposed.

GET api/blog/categories

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/categories" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/categories"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (422):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 115
access-control-allow-origin: *
 

{
    "message": "A content locale is required (e.g. tr or en).",
    "errors": {
        "locale": [
            "A content locale is required (e.g. tr or en)."
        ]
    }
}
 

Request      

GET api/blog/categories

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/blog/tags

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/tags" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"search\": \"b\"
}"
const url = new URL(
    "https://dev.permbot.net/api/blog/tags"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "search": "b"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (422):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 114
access-control-allow-origin: *
 

{
    "message": "A content locale is required (e.g. tr or en).",
    "errors": {
        "locale": [
            "A content locale is required (e.g. tr or en)."
        ]
    }
}
 

Request      

GET api/blog/tags

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

search   string  optional    

Must not be greater than 255 characters. Example: b

GET api/blog/posts

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/posts" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/posts"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (422):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 113
access-control-allow-origin: *
 

{
    "message": "The Locale field is required.",
    "errors": {
        "locale": [
            "The Locale field is required."
        ]
    }
}
 

Request      

GET api/blog/posts

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/blog/posts/{slug}

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/posts/0cl" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/posts/0cl"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (422):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 112
access-control-allow-origin: *
 

{
    "message": "A content locale is required (e.g. tr or en).",
    "errors": {
        "locale": [
            "A content locale is required (e.g. tr or en)."
        ]
    }
}
 

Request      

GET api/blog/posts/{slug}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

slug   string     

The slug of the post. Example: 0cl

Blog management

GET api/blog/manage/categories

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/manage/categories" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/categories"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/blog/manage/categories

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/blog/manage/categories

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/blog/manage/categories" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"b\",
    \"slug\": \"n\"
}"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/categories"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "b",
    "slug": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/blog/manage/categories

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

locale   string  optional    
name   string     

Must not be greater than 255 characters. Example: b

slug   string  optional    

Must contain only letters, numbers, dashes and underscores. Must not be greater than 255 characters. Example: n

PATCH api/blog/manage/categories/{categoryId}

requires authentication

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/blog/manage/categories/architecto" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"b\",
    \"slug\": \"n\"
}"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/categories/architecto"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "b",
    "slug": "n"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/blog/manage/categories/{categoryId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

categoryId   string     

Example: architecto

Body Parameters

name   string  optional    

Must not be greater than 255 characters. Example: b

slug   string  optional    

Must contain only letters, numbers, dashes and underscores. Must not be greater than 255 characters. Example: n

DELETE api/blog/manage/categories/{categoryId}

requires authentication

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/blog/manage/categories/architecto" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/categories/architecto"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/blog/manage/categories/{categoryId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

categoryId   string     

Example: architecto

GET api/blog/manage/tags

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/manage/tags" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"search\": \"b\"
}"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/tags"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "search": "b"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/blog/manage/tags

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

search   string  optional    

Must not be greater than 255 characters. Example: b

POST api/blog/manage/tags

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/blog/manage/tags" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"b\",
    \"slug\": \"n\"
}"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/tags"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "b",
    "slug": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/blog/manage/tags

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

locale   string  optional    
name   string     

Must not be greater than 255 characters. Example: b

slug   string  optional    

Must contain only letters, numbers, dashes and underscores. Must not be greater than 255 characters. Example: n

GET api/blog/manage/posts

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/manage/posts" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/posts"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/blog/manage/posts

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/blog/manage/posts

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/blog/manage/posts" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/posts"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/blog/manage/posts

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/blog/manage/posts/{postId}

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/blog/manage/posts/564" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/posts/564"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/blog/manage/posts/{postId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

postId   string     

Example: 564

PATCH api/blog/manage/posts/{postId}

requires authentication

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/blog/manage/posts/564" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/posts/564"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "PATCH",
    headers,
}).then(response => response.json());

Request      

PATCH api/blog/manage/posts/{postId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

postId   string     

Example: 564

DELETE api/blog/manage/posts/{postId}

requires authentication

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/blog/manage/posts/564" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/posts/564"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/blog/manage/posts/{postId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

postId   string     

Example: 564

POST api/blog/manage/media

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/blog/manage/media" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "file=@/private/var/folders/gb/r30mg0d93vq6d6mm0zjg9q8r0000gp/T/php0jmte8d77ru7dGQKfvw" 
const url = new URL(
    "https://dev.permbot.net/api/blog/manage/media"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Request      

POST api/blog/manage/media

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

file   file     

Must be a file. Example: /private/var/folders/gb/r30mg0d93vq6d6mm0zjg9q8r0000gp/T/php0jmte8d77ru7dGQKfvw

Consent records

Browse append-only visitor consent logs for sites in scope and generate evidence documents (for example when presenting records for a data-subject request). List views show truncated IP addresses; document export includes the stored anonymized IP used for matching.

requires authentication

Returns individual consent log rows (not aggregated analytics). IP addresses are truncated in the list; use an exact visitor IP in ip_address to search (matched after the same anonymization applied at ingestion).

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/consent-records?site_id=3&client_id=12&consent_uid=550e8400-e29b-41d4-a716-446655440000&ip_address=198.51.100.42&from=2026-05-01&to=2026-05-31&sort=id&order=desc&per_page=20" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/consent-records"
);

const params = {
    "site_id": "3",
    "client_id": "12",
    "consent_uid": "550e8400-e29b-41d4-a716-446655440000",
    "ip_address": "198.51.100.42",
    "from": "2026-05-01",
    "to": "2026-05-31",
    "sort": "id",
    "order": "desc",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": 1,
            "site_id": 3,
            "site_domain": "example.com",
            "site_company": "Example Ltd",
            "consent_uid": "550e8400-e29b-41d4-a716-446655440000",
            "preference": "accepted_all",
            "accepted_categories": {
                "essential": true,
                "analytics": true,
                "marketing": true
            },
            "policy_version": 1,
            "ip_address": "198.51.xxx.xxx",
            "integrity_hash": "abc",
            "created_at": "2026-05-19T12:00:00.000000Z",
            "updated_at": "2026-05-19T12:00:00.000000Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1,
        "filters": {
            "site_id": null,
            "client_id": null
        }
    }
}
 

requires authentication

Builds a structured JSON document of all matching consent rows for legal presentation. Requires ip_address; optionally narrow with consent_uid, date range, and site scope. Stored anonymized IPs are included in full (as persisted at ingestion).

Example request:
curl --request POST \
    "https://dev.permbot.net/api/consent-records/document" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"ip_address\": \"198.51.100.42\",
    \"consent_uid\": \"550e8400-e29b-41d4-a716-446655440000\",
    \"site_id\": 3,
    \"client_id\": 12,
    \"from\": \"2026-05-01\",
    \"to\": \"2026-05-31\"
}"
const url = new URL(
    "https://dev.permbot.net/api/consent-records/document"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "ip_address": "198.51.100.42",
    "consent_uid": "550e8400-e29b-41d4-a716-446655440000",
    "site_id": 3,
    "client_id": 12,
    "from": "2026-05-01",
    "to": "2026-05-31"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "generated_at": "2026-05-19T12:00:00.000000Z",
        "generated_by": {
            "id": 1,
            "name": "Agency User",
            "email": "agency@example.com",
            "type": "agency"
        },
        "criteria": {
            "site_id": null,
            "client_id": null,
            "ip_address_masked": "198.51.100.0",
            "consent_uid": null,
            "from": null,
            "to": null
        },
        "record_count": 1,
        "records": [],
        "disclaimer": "..."
    }
}
 

Cookie dictionary

Read-only reference catalogue of known cookie names and patterns used during scans and widget copy.

requires authentication

Filters, free-text search, and sort may be combined in a single request.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/cookie-dictionary?q=google&name%5Blike%5D=_ga&name%5Beq%5D=_ga&provider%5Blike%5D=Google&provider%5Beq%5D=Google&category%5Blike%5D=analytics&category%5Beq%5D=analytics&source%5Beq%5D=manual&sort=name&order=asc&per_page=20" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/cookie-dictionary"
);

const params = {
    "q": "google",
    "name[like]": "_ga",
    "name[eq]": "_ga",
    "provider[like]": "Google",
    "provider[eq]": "Google",
    "category[like]": "analytics",
    "category[eq]": "analytics",
    "source[eq]": "manual",
    "sort": "name",
    "order": "asc",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": 1,
            "name": "_ga",
            "name_pattern": null,
            "match_type": "exact",
            "provider": "Google",
            "category": "analytics",
            "description_en": "Google Analytics cookie used to distinguish users and store client ID.",
            "description_tr": "Google Analytics tarafından kullanıcıları ayırt etmek ve istemci kimliğini saklamak için kullanılan çerez.",
            "source": "manual",
            "open_cookie_database_id": null,
            "created_at": "2026-05-19T10:00:00.000000Z",
            "updated_at": "2026-05-19T10:00:00.000000Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
 

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/cookie-dictionary/1" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/cookie-dictionary/1"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "id": 1,
        "name": "_ga",
        "name_pattern": null,
        "match_type": "exact",
        "provider": "Google",
        "category": "analytics",
        "description_en": "Google Analytics cookie used to distinguish users and store client ID.",
        "description_tr": "Google Analytics tarafından kullanıcıları ayırt etmek ve istemci kimliğini saklamak için kullanılan çerez.",
        "source": "manual",
        "open_cookie_database_id": null,
        "created_at": "2026-05-19T10:00:00.000000Z",
        "updated_at": "2026-05-19T10:00:00.000000Z"
    }
}
 

Dashboard statistics

Aggregated metrics for agency and client dashboards. All endpoints resolve a date window automatically: invalid or missing date inputs fall back to the current month (overview, site statuses) or current year (consent preferences).

Dashboard overview KPIs

requires authentication

Site status counts reflect the current fleet snapshot within the optional site/client scope. Period-bound metrics (sites_created, failed_scan_runs, consent_events) use the resolved date window.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/stats/overview?year=2026&month=5&from=2026-05-01&to=2026-05-31&site_id=3&client_id=12" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/stats/overview"
);

const params = {
    "year": "2026",
    "month": "5",
    "from": "2026-05-01",
    "to": "2026-05-31",
    "site_id": "3",
    "client_id": "12",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "meta": {
        "period": {
            "from": "2026-05-01",
            "to": "2026-05-31",
            "granularity": "month"
        },
        "comparison_period": {
            "from": "2026-04-01",
            "to": "2026-04-30"
        },
        "filters": {
            "site_id": null,
            "client_id": null
        }
    },
    "data": {
        "total_sites": 24,
        "active_sites": 18,
        "pending_sites": 3,
        "queued_sites": 1,
        "scanning_sites": 1,
        "failed_sites": 1,
        "in_progress_sites": 2,
        "sites_created": 2,
        "sites_created_change": 2,
        "failed_scan_runs": 1,
        "consent_events": 450,
        "average_consent_rate": 84.3,
        "managed_clients": 8
    }
}
 

Request      

GET api/stats/overview

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

year   integer  optional    

Calendar year (used with month, or alone for a full-year window). Example: 2026

month   integer  optional    

Calendar month 1–12. Example: 5

from   string  optional    

date Start date (Y-m-d). Takes precedence over year / month when paired with to. Example: 2026-05-01

to   string  optional    

date End date (Y-m-d). Example: 2026-05-31

site_id   integer  optional    

Restrict stats to one visible site. Cannot be combined with client_id. Example: 3

client_id   integer  optional    

Restrict stats to sites owned by this user (agency: self or managed client). Example: 12

Site status distribution

requires authentication

Returns the current status breakdown for all sites in scope. The resolved period is included in meta for UI date pickers; status counts are a live snapshot (not filtered by site creation date).

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/stats/site-statuses?year=2026&month=5&from=2026-05-01&to=2026-05-31&site_id=3&client_id=12" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/stats/site-statuses"
);

const params = {
    "year": "2026",
    "month": "5",
    "from": "2026-05-01",
    "to": "2026-05-31",
    "site_id": "3",
    "client_id": "12",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "meta": {
        "period": {
            "from": "2026-05-01",
            "to": "2026-05-31",
            "granularity": "month"
        },
        "filters": {
            "site_id": null,
            "client_id": null
        }
    },
    "data": {
        "total": 24,
        "in_progress": 2,
        "by_status": {
            "pending": 3,
            "queued": 1,
            "scanning": 1,
            "active": 18,
            "failed": 1
        }
    }
}
 

Request      

GET api/stats/site-statuses

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

year   integer  optional    

Example: 2026

month   integer  optional    

Example: 5

from   string  optional    

date Example: 2026-05-01

to   string  optional    

date Example: 2026-05-31

site_id   integer  optional    

Example: 3

client_id   integer  optional    

Example: 12

requires authentication

Each consent log row is classified as accepted_all, partially_accepted, or rejected from normalized accepted_categories. Defaults to the current calendar year when no valid date input is sent.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/stats/consent-preferences?year=2026&month=5&from=2026-01-01&to=2026-12-31&site_id=3&client_id=12" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/stats/consent-preferences"
);

const params = {
    "year": "2026",
    "month": "5",
    "from": "2026-01-01",
    "to": "2026-12-31",
    "site_id": "3",
    "client_id": "12",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "meta": {
        "period": {
            "from": "2026-01-01",
            "to": "2026-12-31",
            "granularity": "year"
        },
        "filters": {
            "site_id": null,
            "client_id": null
        }
    },
    "data": {
        "months": [
            {
                "month": "2026-01",
                "accepted_all": 1200,
                "partially_accepted": 200,
                "rejected": 300,
                "total": 1700
            }
        ],
        "totals": {
            "accepted_all": 1200,
            "partially_accepted": 200,
            "rejected": 300,
            "total": 1700
        }
    }
}
 

Endpoints

Authenticate the request for channel access.

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/broadcasting/auth" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/broadcasting/auth"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/broadcasting/auth

POST api/broadcasting/auth

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/system/status

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/system/status" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/system/status"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "data": {
        "status": "operational",
        "database_connected": true,
        "database": "permbot",
        "redis_connected": true,
        "queue_connected": true,
        "tables": {
            "notifications": true,
            "activity_logs": true,
            "site_verification_runs": true,
            "cookie_consents": true,
            "sites": true
        },
        "consent_databases": {
            "consent_tr": {
                "connected": true,
                "database": "permbot_consent_tr",
                "cookie_consents": true
            }
        },
        "checked_at": "2026-06-03T11:52:29+00:00"
    }
}
 

Request      

GET api/system/status

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Serves hardened vanilla JS bundles keyed by **`sites.script_id`** (UUID).

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/widget/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc.js" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/widget/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc.js"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):

Show headers
content-type: application/javascript; charset=UTF-8
x-robots-tag: noindex
cache-control: no-cache, private
x-ratelimit-limit: 240
x-ratelimit-remaining: 239
 

// Permbot: unknown bundle identifier

 

Request      

GET api/v1/widget/{script_id}.js

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

script_id   string     

The ID of the script. Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

requires authentication

404: No row for this consent_uid on this site — embedded widgets clear matching localStorage and show the CMP again.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/widget/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/latest-consent" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"consent_uid\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\"
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/widget/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/latest-consent"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "consent_uid": "6ff8f7f6-1eb3-3525-be4a-3932c805afed"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 240
x-ratelimit-remaining: 238
 

{
    "message": "Consent record could not be found."
}
 

Visitor-facing catalogue of declared storage signals grouped by CMP category (localized descriptions).

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/widget/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/cookies" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/widget/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/cookies"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 240
x-ratelimit-remaining: 237
 

{
    "message": "Consent record could not be found."
}
 

Request      

GET api/v1/widget/{script_id}/cookies

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

script_id   string     

The ID of the script. Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

POST api/v1/consents

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/v1/consents" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"site_id\": 16,
    \"consent_uid\": \"a4855dc5-0acb-33c3-b921-f4291f719ca0\",
    \"accepted_categories\": {
        \"analytics\": false,
        \"marketing\": true
    }
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/consents"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "site_id": 16,
    "consent_uid": "a4855dc5-0acb-33c3-b921-f4291f719ca0",
    "accepted_categories": {
        "analytics": false,
        "marketing": true
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/consents

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

site_id   integer     

The id of an existing record in the sites table. Example: 16

consent_uid   string     

Must be a valid UUID. Example: a4855dc5-0acb-33c3-b921-f4291f719ca0

accepted_categories   object     
analytics   boolean  optional    

Example: false

marketing   boolean  optional    

Example: true

GET api/billing/iyzico/callback

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/billing/iyzico/callback" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/billing/iyzico/callback"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (302):

Show headers
cache-control: no-cache, private
location: https://ui.permbot.net/dashboard/billing/payment-result?status=error&reason=missing_token
content-type: text/html; charset=utf-8
access-control-allow-origin: *
 

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="refresh" content="0;url='https://ui.permbot.net/dashboard/billing/payment-result?status=error&amp;reason=missing_token'" />

        <title>Redirecting to https://ui.permbot.net/dashboard/billing/payment-result?status=error&amp;reason=missing_token</title>
    </head>
    <body>
        Redirecting to <a href="https://ui.permbot.net/dashboard/billing/payment-result?status=error&amp;reason=missing_token">https://ui.permbot.net/dashboard/billing/payment-result?status=error&amp;reason=missing_token</a>.
    </body>
</html>
 

Request      

GET api/billing/iyzico/callback

POST api/billing/iyzico/callback

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/realtime/config

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/realtime/config" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/realtime/config"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/realtime/config

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/realtime/live-activity

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/realtime/live-activity" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"limit\": 16
}"
const url = new URL(
    "https://dev.permbot.net/api/realtime/live-activity"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "limit": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/realtime/live-activity

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

limit   integer  optional    

Must be at least 1. Example: 16

GET api/data-locations

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/data-locations" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/data-locations"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/data-locations

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Start an ephemeral multi-page cookie probe for onboarding / demo flows.

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/v1/public/quick-scans" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"domain\": \"b\",
    \"max_pages\": 2,
    \"basic_auth\": {
        \"username\": \"g\",
        \"password\": \"{+-0pBNvYgx\"
    }
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "domain": "b",
    "max_pages": 2,
    "basic_auth": {
        "username": "g",
        "password": "{+-0pBNvYgx"
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/public/quick-scans

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

domain   string     

Must not be greater than 2048 characters. Example: b

max_pages   integer  optional    

Must be at least 1. Must not be greater than 10. Example: 2

basic_auth   object  optional    
username   string  optional    

This field is required when basic_auth is present. Must not be greater than 255 characters. Example: g

password   string  optional    

Must not be greater than 1024 characters. Example: {+-0pBNvYgx

Poll scan status and, when finished, cookie harvest details.

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 30
x-ratelimit-remaining: 20
 

{
    "message": "Invalid or expired request signature."
}
 

Request      

GET api/v1/public/quick-scans/{scanId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

scanId   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

GET api/v1/public/quick-scans/{scanId}/widget

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/widget" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/widget"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 30
x-ratelimit-remaining: 19
 

{
    "message": "Invalid or expired request signature."
}
 

Request      

GET api/v1/public/quick-scans/{scanId}/widget

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

scanId   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

PATCH api/v1/public/quick-scans/{scanId}/widget

requires authentication

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/widget" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/widget"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "PATCH",
    headers,
}).then(response => response.json());

Request      

PATCH api/v1/public/quick-scans/{scanId}/widget

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

scanId   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

GET api/v1/public/quick-scans/{scanId}/preview

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/preview" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/preview"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 30
x-ratelimit-remaining: 18
 

{
    "message": "Invalid or expired request signature."
}
 

Request      

GET api/v1/public/quick-scans/{scanId}/preview

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

scanId   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

GET api/v1/public/quick-scans/{scanId}/cookies

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/cookies" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/cookies"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 107
 

{
    "message": "Quick scan session could not be found or has expired."
}
 

Request      

GET api/v1/public/quick-scans/{scanId}/cookies

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

scanId   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

GET api/v1/public/quick-scans/{scanId}/widget.js

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/widget.js" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/widget.js"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 106
 

{
    "message": "Quick scan session could not be found or has expired."
}
 

Request      

GET api/v1/public/quick-scans/{scanId}/widget.js

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

scanId   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/latest-consent" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"consent_uid\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\"
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/latest-consent"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "consent_uid": "6ff8f7f6-1eb3-3525-be4a-3932c805afed"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 105
 

{
    "message": "Consent record could not be found."
}
 

POST api/v1/public/quick-scans/{scanId}/consents

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/consents" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"consent_uid\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
    \"accepted_categories\": {
        \"analytics\": true,
        \"marketing\": true
    },
    \"policy_version\": 16
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/public/quick-scans/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/consents"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "consent_uid": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
    "accepted_categories": {
        "analytics": true,
        "marketing": true
    },
    "policy_version": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/public/quick-scans/{scanId}/consents

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

scanId   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

Body Parameters

consent_uid   string     

Must be a valid UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

accepted_categories   object     
analytics   boolean  optional    

Example: true

marketing   boolean  optional    

Example: true

policy_version   integer  optional    

Must be at least 1. Example: 16

Integrity timestamps

Daily TÜBİTAK-stamped integrity hash chain batches for the authenticated billing account.

List integrity timestamp batches

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/integrity-timestamps?from=2026-05-01&to=2026-05-31&consent_location=tr&status=completed&per_page=20" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/integrity-timestamps"
);

const params = {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "consent_location": "tr",
    "status": "completed",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/integrity-timestamps

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

from   string  optional    

date Filter period_date from (Y-m-d). Example: 2026-05-01

to   string  optional    

date Filter period_date to (Y-m-d). Example: 2026-05-31

consent_location   string  optional    

Filter by consent data location. Example: tr

status   string  optional    

Filter by status. Example: completed

per_page   integer  optional    

Items per page (max 50). Example: 20

Show integrity timestamp batch detail

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/integrity-timestamps/architecto" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/integrity-timestamps/architecto"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/integrity-timestamps/{id}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the integrity timestamp. Example: architecto

Download integrity timestamp report archive (ZIP)

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/integrity-timestamps/architecto/download" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/integrity-timestamps/architecto/download"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/integrity-timestamps/{id}/download

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the integrity timestamp. Example: architecto

Notifications

In-app notifications for authenticated users (database channel). Site-related events are delivered to the site owner and, when the owner is an agency child client, to the managing agency as well.

List notifications for the authenticated user

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/notifications?unread_only=1&per_page=20" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"unread_only\": false,
    \"per_page\": 1
}"
const url = new URL(
    "https://dev.permbot.net/api/admin/notifications"
);

const params = {
    "unread_only": "1",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "unread_only": false,
    "per_page": 1
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": "9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
            "type": "site_created",
            "title": "Site added",
            "body": "Plumbing Co LLC (plumbing.example) was added.",
            "context": {
                "site_id": 1
            },
            "read_at": null,
            "created_at": "2026-05-19T12:00:00.000000Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
 

Request      

GET api/admin/notifications

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

unread_only   boolean  optional    

When true, return only unread notifications. Example: true

per_page   integer  optional    

Items per page (max 50). Example: 20

Body Parameters

unread_only   boolean  optional    

Example: false

per_page   integer  optional    

Must be at least 1. Must not be greater than 50. Example: 1

Unread notification count

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/admin/notifications/unread-count" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/notifications/unread-count"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "unread_count": 3
    }
}
 

Request      

GET api/admin/notifications/unread-count

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Mark all notifications as read

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/notifications/read-all" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/notifications/read-all"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "marked_read": 3
    },
    "message": "All notifications marked as read."
}
 

Request      

POST api/admin/notifications/read-all

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Mark a single notification as read

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/admin/notifications/9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e/read" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/admin/notifications/9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e/read"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "id": "9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
        "type": "site_created",
        "title": "Site added",
        "body": "Plumbing Co LLC (plumbing.example) was added.",
        "context": {
            "site_id": 1
        },
        "read_at": "2026-05-19T12:05:00.000000Z",
        "created_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

POST api/admin/notifications/{notificationId}/read

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

notificationId   string     

Notification UUID. Example: 9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e

List notifications for the authenticated user

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/notifications?unread_only=1&per_page=20" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"unread_only\": false,
    \"per_page\": 1
}"
const url = new URL(
    "https://dev.permbot.net/api/notifications"
);

const params = {
    "unread_only": "1",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "unread_only": false,
    "per_page": 1
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": "9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
            "type": "site_created",
            "title": "Site added",
            "body": "Plumbing Co LLC (plumbing.example) was added.",
            "context": {
                "site_id": 1
            },
            "read_at": null,
            "created_at": "2026-05-19T12:00:00.000000Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
 

Request      

GET api/notifications

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

unread_only   boolean  optional    

When true, return only unread notifications. Example: true

per_page   integer  optional    

Items per page (max 50). Example: 20

Body Parameters

unread_only   boolean  optional    

Example: false

per_page   integer  optional    

Must be at least 1. Must not be greater than 50. Example: 1

Unread notification count

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/notifications/unread-count" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/notifications/unread-count"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "unread_count": 3
    }
}
 

Request      

GET api/notifications/unread-count

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Mark all notifications as read

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/notifications/read-all" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/notifications/read-all"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "marked_read": 3
    },
    "message": "All notifications marked as read."
}
 

Request      

POST api/notifications/read-all

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Mark a single notification as read

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/notifications/9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e/read" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/notifications/9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e/read"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "id": "9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
        "type": "site_created",
        "title": "Site added",
        "body": "Plumbing Co LLC (plumbing.example) was added.",
        "context": {
            "site_id": 1
        },
        "read_at": "2026-05-19T12:05:00.000000Z",
        "created_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

POST api/notifications/{notificationId}/read

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

notificationId   string     

Notification UUID. Example: 9b2c4f0e-8a1d-4b3c-9d0e-1f2a3b4c5d6e

Public pricing

Marketing site pricing tables. No authentication required.

GET api/public/packages

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/public/packages" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"audience\": \"architecto\"
}"
const url = new URL(
    "https://dev.permbot.net/api/public/packages"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "audience": "architecto"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (422):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 116
access-control-allow-origin: *
 

{
    "message": "The selected audience is invalid.",
    "errors": {
        "audience": [
            "The selected audience is invalid."
        ]
    }
}
 

Request      

GET api/public/packages

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

audience   string  optional    

Example: architecto

Sites

Sites belong to exactly one users record (sites.user_id). Agencies may optionally associate a newly created site with one of their child clients (client_id), effectively storing the client as the owning user row.

Cookie discovery runs only after POST /api/sites/{siteId}/cookie-scan dispatches DiscoverAndSaveCookiesJob to the queue (pending, queued, scanning, active, failed).

List sites visible to the authenticated user

requires authentication

Filters and sort may be combined in a single request.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/sites?status%5Beq%5D=active&status%5Bneq%5D=failed&user_id=12&owner_id=12&sort=company_name&order=asc" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites"
);

const params = {
    "status[eq]": "active",
    "status[neq]": "failed",
    "user_id": "12",
    "owner_id": "12",
    "sort": "company_name",
    "order": "asc",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": [
        {
            "id": 1,
            "user_id": 2,
            "company_name": "Plumbing Co LLC",
            "domain_name": "plumbing.example",
            "script_id": "9b7c4f2e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
            "status": "active",
            "settings": null,
            "last_scan_requested_at": "2026-05-19T11:00:00.000000Z",
            "last_scan_started_at": "2026-05-19T11:00:05.000000Z",
            "last_scan_finished_at": "2026-05-19T11:02:00.000000Z",
            "last_scan_error": null,
            "current_policy_version": 1,
            "created_at": "2026-05-19T10:00:00.000000Z",
            "updated_at": "2026-05-19T11:02:00.000000Z",
            "owner": {
                "id": 2,
                "name": "Client User",
                "email": "client@example.com",
                "type": "client",
                "parent_id": 1
            }
        }
    ]
}
 

Request      

GET api/sites

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status[eq]   string  optional    

Filter sites where status equals this value (pending, queued, scanning, active, failed). Example: active

status[neq]   string  optional    

Filter sites where status is not equal to this value. Cannot be sent together with status[eq]. Example: failed

user_id   integer  optional    

Filter by owning user (sites.user_id). Example: 12

owner_id   integer  optional    

Alias for user_id (same column). Example: 12

sort   string  optional    

Sort column: id, created_at, updated_at, company_name, domain_name, status, user_id, last_scan_requested_at, last_scan_finished_at. Default: id. Example: company_name

order   string  optional    

Sort direction: asc or desc. Default: desc. Example: asc

Create a site

requires authentication

Sets status to pending. Cookie scanning does not start automatically — call POST /api/sites/{siteId}/cookie-scan to enqueue DiscoverAndSaveCookiesJob. Site verification (reachability + widget script) is queued automatically and does not affect sites.status.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/sites" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"company_name\": \"Plumbing Co LLC\",
    \"domain_name\": \"plumbing.example\",
    \"settings\": {
        \"theme\": \"dark\"
    },
    \"client_id\": 12,
    \"consent_location\": \"architecto\",
    \"disabled_pages\": [
        \"n\"
    ],
    \"share_consent_across_subdomains\": true
}"
const url = new URL(
    "https://dev.permbot.net/api/sites"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "company_name": "Plumbing Co LLC",
    "domain_name": "plumbing.example",
    "settings": {
        "theme": "dark"
    },
    "client_id": 12,
    "consent_location": "architecto",
    "disabled_pages": [
        "n"
    ],
    "share_consent_across_subdomains": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201, success):


{
    "data": {
        "id": 1,
        "user_id": 1,
        "company_name": "Plumbing Co LLC",
        "domain_name": "plumbing.example",
        "script_id": "9b7c4f2e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
        "status": "pending",
        "settings": null,
        "last_scan_requested_at": null,
        "last_scan_started_at": null,
        "last_scan_finished_at": null,
        "last_scan_error": null,
        "current_policy_version": 0,
        "created_at": "2026-05-19T12:00:00.000000Z",
        "updated_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

POST api/sites

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

company_name   string     

Example: Plumbing Co LLC

domain_name   string     

Example: plumbing.example

settings   object  optional    

Optional JSON stored on the site (sites.settings). Widget bundle reads settings.widget, e.g. {"widget":{"banner":{"show_reject_optional":false},"reopen_preferences":{"show_floating_link":true},"copy":{"en":{"title":"We value your privacy","intro_html":"<p>...</p>"}}}}.

client_id   integer  optional    

Optional (agencies only). Must reference a client child of the authenticated agency. Example: 12

consent_location   string  optional    

Example: architecto

disabled_pages   string[]  optional    

Must not be greater than 2048 characters.

share_consent_across_subdomains   boolean  optional    

Example: true

Update site fields visible to the authenticated owner (or an agency managing the client owner).

requires authentication

Send only keys you want to change. When settings is present, it is deep-merged into existing sites.settings (array_replace_recursive), so nested objects such as widget.theme are preserved unless overridden by the patch. Set settings to null to clear all stored settings for the site.

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/sites/1" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"company_name\": \"Plumbing Co LLC\",
    \"domain_name\": \"plumbing.example\",
    \"settings\": {
        \"widget\": {
            \"banner\": {
                \"show_reject_optional\": false
            }
        }
    },
    \"consent_location\": \"architecto\",
    \"disabled_pages\": [
        \"n\"
    ],
    \"share_consent_across_subdomains\": false
}"
const url = new URL(
    "https://dev.permbot.net/api/sites/1"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "company_name": "Plumbing Co LLC",
    "domain_name": "plumbing.example",
    "settings": {
        "widget": {
            "banner": {
                "show_reject_optional": false
            }
        }
    },
    "consent_location": "architecto",
    "disabled_pages": [
        "n"
    ],
    "share_consent_across_subdomains": false
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "id": 1,
        "user_id": 1,
        "company_name": "Plumbing Co LLC",
        "domain_name": "plumbing.example",
        "script_id": "9b7c4f2e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
        "status": "pending",
        "settings": {
            "widget": {
                "banner": {
                    "show_reject_optional": false
                }
            }
        },
        "last_scan_requested_at": null,
        "last_scan_started_at": null,
        "last_scan_finished_at": null,
        "last_scan_error": null,
        "current_policy_version": 0,
        "created_at": "2026-05-19T12:00:00.000000Z",
        "updated_at": "2026-05-19T12:10:00.000000Z"
    }
}
 

Request      

PATCH api/sites/{siteId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

Body Parameters

company_name   string  optional    

optional Example: Plumbing Co LLC

domain_name   string  optional    

optional Example: plumbing.example

settings   object  optional    

optional Partial JSON merged into sites.settings.

consent_location   string  optional    

Example: architecto

disabled_pages   string[]  optional    

Must not be greater than 2048 characters.

share_consent_across_subdomains   boolean  optional    

Example: false

Delete a site

requires authentication

When the site has no visitor consent rows, the site row and related central data (signals, scan runs, verification runs) are removed permanently. When consent history exists, the site is archived (deleted_at set): it no longer appears for the owner, does not count toward subscription site limits, and the public widget stops serving for its script_id. Admins and super-admins can still list and open archived sites (is_deleted = true).

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/sites/1" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/1"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200, purged):


{
    "data": {
        "site_id": 1,
        "deletion": "purged"
    }
}
 

Example response (200, archived):


{
    "data": {
        "site_id": 1,
        "deletion": "archived"
    }
}
 

Example response (409, already deleted):


{
    "message": "This site has already been removed."
}
 

Request      

DELETE api/sites/{siteId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

Site detail (**summary** + counters, recent signals, last scan runs)

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/sites/1" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/1"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "site": {
            "id": 1,
            "user_id": 2,
            "company_name": "Plumbing Co LLC",
            "domain_name": "plumbing.example",
            "script_id": "9b7c4f2e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
            "status": "active",
            "settings": null,
            "last_scan_requested_at": "2026-05-19T11:00:00.000000Z",
            "last_scan_started_at": "2026-05-19T11:00:05.000000Z",
            "last_scan_finished_at": "2026-05-19T11:02:00.000000Z",
            "last_scan_error": null,
            "created_at": "2026-05-19T10:00:00.000000Z",
            "updated_at": "2026-05-19T11:02:00.000000Z",
            "signals_count": 1
        },
        "owner": {
            "id": 2,
            "name": "Client User",
            "email": "client@example.com",
            "type": "client",
            "parent_id": 1
        },
        "signal_summary": {
            "by_type": {
                "cookie": 1
            },
            "by_category": {
                "analytics": 1
            },
            "by_discovery_source": {
                "scanner": 1
            }
        },
        "recent_signals": [
            {
                "id": 1,
                "type": "cookie",
                "discovery_source": "scanner",
                "name": "_ga",
                "category": "analytics",
                "cookie_domain": ".plumbing.example",
                "expires_at": null,
                "visible_to_users": true,
                "updated_at": "2026-05-19T11:02:00.000000Z",
                "created_at": "2026-05-19T11:02:00.000000Z"
            }
        ],
        "recent_scan_runs": [
            {
                "id": 1,
                "status": "completed",
                "started_at": "2026-05-19T11:00:05.000000Z",
                "finished_at": "2026-05-19T11:02:00.000000Z",
                "pages_visited": 3,
                "scanner_cookie_rows": 1,
                "local_storage_rows": 0,
                "session_storage_rows": 0,
                "indexed_db_rows": 0,
                "total_signals_written": 1,
                "targets_snapshot": [
                    "https://plumbing.example/"
                ],
                "error_summary": null,
                "created_at": "2026-05-19T11:00:05.000000Z"
            }
        ]
    }
}
 

Request      

GET api/sites/{siteId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

requires authentication

Queues DiscoverAndSaveCookiesJob and sets status to queued while the worker has not begun yet. Returns 409 while scanning; 422 while already queued.

Example request:
curl --request POST \
    "https://dev.permbot.net/api/sites/1/cookie-scan" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/1/cookie-scan"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202, accepted):


{
    "data": {
        "site_id": 1,
        "status": "queued",
        "last_scan_requested_at": "2026-05-19T12:00:00.000000Z",
        "message": "Cookie scan queued."
    }
}
 

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/sites/2/cookie-scan" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/2/cookie-scan"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Add a manual site signal row (**`discovery_source`** = **`manual`**; never replaced by crawler).

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/sites/2/site-cookies" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"cookie\",
    \"name\": \"loyalty_token\",
    \"value\": \"\\\"\\\"\",
    \"cookie_domain\": \"`.example.com`\",
    \"expires_at\": \"2027-05-01T00:00:00Z\",
    \"category\": \"marketing\",
    \"description_en\": \"Loyalty vendor token\",
    \"description_tr\": \"Loyalty reward component note\"
}"
const url = new URL(
    "https://dev.permbot.net/api/sites/2/site-cookies"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "cookie",
    "name": "loyalty_token",
    "value": "\"\"",
    "cookie_domain": "`.example.com`",
    "expires_at": "2027-05-01T00:00:00Z",
    "category": "marketing",
    "description_en": "Loyalty vendor token",
    "description_tr": "Loyalty reward component note"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201, success):


{
    "data": {
        "id": 1,
        "site_id": 1,
        "type": "cookie",
        "name": "loyalty_token",
        "value": "",
        "cookie_domain": ".example.com",
        "expires_at": null,
        "category": "marketing",
        "description_en": "Loyalty vendor token",
        "description_tr": null,
        "discovery_source": "manual",
        "visible_to_users": true,
        "created_at": "2026-05-19T12:00:00.000000Z",
        "updated_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

POST api/sites/{siteId}/site-cookies

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Example: 2

Body Parameters

type   string     

One of cookie, local_storage, session_storage, indexed_db. Example: cookie

name   string     

Example: loyalty_token

value   string  optional    

Optional (often empty for non-cookie types). Example: ""

cookie_domain   string  optional    

optional Example: .example.com

expires_at   string  optional    

optional ISO-8601 datetime Example: 2027-05-01T00:00:00Z

category   string  optional    

optional Defaults uncategorized. Example: marketing

description_en   string  optional    

optional Example: Loyalty vendor token

description_tr   string  optional    

optional Example: Loyalty reward component note

Update editable fields: **`category`**, **`description_*`**, and **`visible_to_users`**.

requires authentication

visible_to_users controls whether this signal participates in end-user cookie-consent UX and downstream consent/logging flows. When false, scanners still persist scanner rows, but integrations may omit the row from banners and exported consent records.

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/sites/2/site-cookies/architecto" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"category\": \"analytics\",
    \"description_en\": \"Audience measurement cookie\",
    \"description_tr\": \"Used for audience measurement\",
    \"visible_to_users\": false
}"
const url = new URL(
    "https://dev.permbot.net/api/sites/2/site-cookies/architecto"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "category": "analytics",
    "description_en": "Audience measurement cookie",
    "description_tr": "Used for audience measurement",
    "visible_to_users": false
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "id": 1,
        "site_id": 1,
        "type": "cookie",
        "name": "_ga",
        "value": "",
        "cookie_domain": ".example.com",
        "expires_at": null,
        "category": "analytics",
        "description_en": "Audience measurement cookie",
        "description_tr": null,
        "discovery_source": "scanner",
        "visible_to_users": false,
        "created_at": "2026-05-19T11:02:00.000000Z",
        "updated_at": "2026-05-19T12:00:00.000000Z"
    }
}
 

Request      

PATCH api/sites/{siteId}/site-cookies/{signalId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Example: 2

signalId   string     

Example: architecto

Body Parameters

category   string  optional    

optional Example: analytics

description_en   string  optional    

optional Example: Audience measurement cookie

description_tr   string  optional    

optional Example: Used for audience measurement

visible_to_users   boolean  optional    

optional When false, omit from end-user-facing consent surfaces and logs. Example: false

Delete a **`manual`** signal row.

requires authentication

Scanner-derived rows cannot be deleted; PATCH with visible_to_users = false removes them from end-user-facing consent workflows instead.

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/sites/2/site-cookies/architecto" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/2/site-cookies/architecto"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204, deleted):

Empty response
 

Example response (403, scanner row):


{}
 

Request      

DELETE api/sites/{siteId}/site-cookies/{signalId}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Example: 2

signalId   string     

Example: architecto

Site verification

Informational checks (site reachability + widget script presence). Does not change sites.status or block usage. Separate from cookie discovery (POST /api/sites/{siteId}/cookie-scan).

Queue a new verification run (async)

requires authentication

Returns 202 when queued. Poll with GET /api/sites/{siteId}/verification. Returns 409 while a run is running; 422 while queued (worker not started yet).

Example request:
curl --request POST \
    "https://dev.permbot.net/api/sites/1/verification" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"basic_auth\": {
        \"username\": \"staging\",
        \"password\": \"secret\"
    }
}"
const url = new URL(
    "https://dev.permbot.net/api/sites/1/verification"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "basic_auth": {
        "username": "staging",
        "password": "secret"
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/sites/{siteId}/verification

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

Body Parameters

basic_auth   object  optional    

optional HTTP Basic Auth for protected staging sites. Merged into settings.scanner.basic_auth before the run starts.

username   string     

with basic_auth Example: staging

password   string  optional    

optional Example: secret

Latest verification run for polling.

requires authentication

When no run exists yet (e.g. legacy sites created before verification shipped), the first GET automatically queues an initial run so polling alone is enough for the UI.

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/sites/1/verification" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/1/verification"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/sites/{siteId}/verification

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

Site widget settings

Dedicated widget configuration for a site: stored overrides, effective merged settings, resolved preview payload, customizable field schema, starter template, and embed URLs. Updates deep-merge into sites.settings.widget only.

Widget configuration bundle for panel editing and live preview

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/sites/1/widget?preview_locale=tr" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/1/widget"
);

const params = {
    "preview_locale": "tr",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "site_id": 1,
        "script_id": "9b7c4f2e-8a1d-4b3c-9d0e-1f2a3b4c5d6e",
        "settings": null,
        "effective": {
            "theme": {
                "appearance": "light"
            }
        },
        "resolved": {
            "locale": "en",
            "banner_title": "Cookie preferences"
        },
        "schema": {
            "locales": [
                "en",
                "tr"
            ],
            "fields": []
        },
        "template": {
            "copy": {
                "en": {
                    "title": "Cookie preferences"
                }
            }
        },
        "defaults": {
            "theme": {
                "appearance": "light"
            }
        },
        "embed": {
            "script_url": "https://example.com/api/v1/widget/9b7c4f2e.js",
            "preview_url": null
        }
    }
}
 

Request      

GET api/sites/{siteId}/widget

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

Query Parameters

preview_locale   string  optional    

Locale used for resolved banner copy (en, tr, …). Default: en. Example: tr

Update widget settings (partial deep merge)

requires authentication

Send only keys you want to change at the widget root (same shape as settings in the GET response). The response includes schema, template, and resolved preview data again.

Example request:
curl --request PATCH \
    "https://dev.permbot.net/api/sites/1/widget?preview_locale=tr" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"consent_location\": \"tr\",
    \"locale\": \"sr_BA\",
    \"copy\": {
        \"en\": {
            \"title\": \"Cookies\",
            \"intro_html\": \"<p>We use cookies.<\\/p>\"
        },
        \"0\": {
            \"title\": \"z\",
            \"intro_html\": \"m\"
        }
    },
    \"cookie_policy_url\": \"https:\\/\\/example.com\\/cookies\",
    \"theme\": {
        \"appearance\": \"dark\",
        \"border_radius_px\": 22,
        \"z_index\": 67,
        \"colors\": {
            \"primary\": \"z\",
            \"primary_hover\": \"m\",
            \"danger\": \"i\",
            \"danger_hover\": \"y\",
            \"surface\": \"v\",
            \"text\": \"d\",
            \"text_muted\": \"l\",
            \"border\": \"j\",
            \"switch_track_off\": \"n\",
            \"switch_track_on\": \"i\",
            \"shadow\": \"k\",
            \"banner_max_width_px\": 14
        }
    },
    \"banner\": {
        \"show_reject_optional\": true
    },
    \"category_hints\": {
        \"copy\": {
            \"tr\": {
                \"essential\": \"…\",
                \"analytics\": \"…\",
                \"marketing\": \"…\"
            },
            \"0\": {
                \"essential\": \"i\",
                \"analytics\": \"y\",
                \"marketing\": \"v\"
            }
        }
    },
    \"reopen_preferences\": {
        \"show_floating_link\": true,
        \"preferences_link_href\": \"w\",
        \"preferences_link_target\": \"architecto\",
        \"invoke_window_function\": \"n\"
    },
    \"disabled_pages\": [
        \"\\/checkout\",
        \"\\/payment\"
    ],
    \"share_consent_across_subdomains\": true
}"
const url = new URL(
    "https://dev.permbot.net/api/sites/1/widget"
);

const params = {
    "preview_locale": "tr",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "consent_location": "tr",
    "locale": "sr_BA",
    "copy": {
        "en": {
            "title": "Cookies",
            "intro_html": "<p>We use cookies.<\/p>"
        },
        "0": {
            "title": "z",
            "intro_html": "m"
        }
    },
    "cookie_policy_url": "https:\/\/example.com\/cookies",
    "theme": {
        "appearance": "dark",
        "border_radius_px": 22,
        "z_index": 67,
        "colors": {
            "primary": "z",
            "primary_hover": "m",
            "danger": "i",
            "danger_hover": "y",
            "surface": "v",
            "text": "d",
            "text_muted": "l",
            "border": "j",
            "switch_track_off": "n",
            "switch_track_on": "i",
            "shadow": "k",
            "banner_max_width_px": 14
        }
    },
    "banner": {
        "show_reject_optional": true
    },
    "category_hints": {
        "copy": {
            "tr": {
                "essential": "…",
                "analytics": "…",
                "marketing": "…"
            },
            "0": {
                "essential": "i",
                "analytics": "y",
                "marketing": "v"
            }
        }
    },
    "reopen_preferences": {
        "show_floating_link": true,
        "preferences_link_href": "w",
        "preferences_link_target": "architecto",
        "invoke_window_function": "n"
    },
    "disabled_pages": [
        "\/checkout",
        "\/payment"
    ],
    "share_consent_across_subdomains": true
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "site_id": 1,
        "settings": {
            "theme": {
                "appearance": "dark"
            }
        },
        "resolved": {
            "locale": "en"
        }
    }
}
 

Request      

PATCH api/sites/{siteId}/widget

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

Query Parameters

preview_locale   string  optional    

Locale for resolved preview copy. Example: tr

Body Parameters

consent_location   string  optional    

optional Consent data residency (tr, …). Locked after the first visitor consent. Example: tr

locale   string  optional    

Example: sr_BA

copy   object  optional    

optional Per-locale banner copy.

title   string  optional    

Must not be greater than 255 characters. Example: z

intro_html   string  optional    

Must not be greater than 10000 characters. Example: m

cookie_policy_url   string  optional    

optional HTTPS policy page. Example: https://example.com/cookies

theme   object  optional    

optional

appearance   string  optional    

Example: architecto

border_radius_px   integer  optional    

Must be at least 0. Must not be greater than 64. Example: 22

z_index   integer  optional    

Must be at least 1. Example: 67

colors   object  optional    
primary   string  optional    

Must not be greater than 32 characters. Example: z

primary_hover   string  optional    

Must not be greater than 32 characters. Example: m

danger   string  optional    

Must not be greater than 64 characters. Example: i

danger_hover   string  optional    

Must not be greater than 64 characters. Example: y

surface   string  optional    

Must not be greater than 64 characters. Example: v

text   string  optional    

Must not be greater than 64 characters. Example: d

text_muted   string  optional    

Must not be greater than 64 characters. Example: l

border   string  optional    

Must not be greater than 64 characters. Example: j

switch_track_off   string  optional    

Must not be greater than 64 characters. Example: n

switch_track_on   string  optional    

Must not be greater than 64 characters. Example: i

shadow   string  optional    

Must not be greater than 128 characters. Example: k

banner_max_width_px   integer  optional    

Must be at least 280. Must not be greater than 960. Example: 14

banner   object  optional    

optional

show_reject_optional   boolean  optional    

Example: true

category_hints   object  optional    

optional Per-locale helper lines under each CMP toggle.

copy   object[]  optional    
essential   string  optional    

Must not be greater than 1000 characters. Example: i

analytics   string  optional    

Must not be greater than 1000 characters. Example: y

marketing   string  optional    

Must not be greater than 1000 characters. Example: v

reopen_preferences   object  optional    

optional

show_floating_link   boolean  optional    

Example: true

preferences_link_href   string  optional    

Must not be greater than 2048 characters. Example: w

preferences_link_target   string  optional    

Example: architecto

invoke_window_function   string  optional    

Must not be greater than 255 characters. Example: n

disabled_pages   string[]  optional    

optional URL paths where the banner must not appear.

share_consent_across_subdomains   boolean  optional    

optional Share consent cookie across subdomains of the site domain. Example: true

Reset widget settings to packaged defaults

requires authentication

Removes sites.settings.widget while preserving unrelated settings keys.

Example request:
curl --request DELETE \
    "https://dev.permbot.net/api/sites/1/widget" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/sites/1/widget"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200, success):


{
    "data": {
        "site_id": 1,
        "settings": null
    }
}
 

Request      

DELETE api/sites/{siteId}/widget

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

siteId   integer     

Site primary key. Example: 1

Support tickets (panel)

Authenticated users can open and follow their own support requests.

GET api/support/tickets

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/support/tickets" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"per_page\": 1
}"
const url = new URL(
    "https://dev.permbot.net/api/support/tickets"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 1
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/support/tickets

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

per_page   integer  optional    

Must be at least 1. Must not be greater than 50. Example: 1

POST api/support/tickets

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/support/tickets" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"subject\": \"b\",
    \"message\": \"n\"
}"
const url = new URL(
    "https://dev.permbot.net/api/support/tickets"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subject": "b",
    "message": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/support/tickets

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

subject   string     

Must not be greater than 255 characters. Example: b

message   string     

Must not be greater than 10000 characters. Example: n

GET api/support/tickets/{ticketUid}

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://dev.permbot.net/api/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/support/tickets/{ticketUid}

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ticketUid   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

POST api/support/tickets/{ticketUid}/messages

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/messages" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"message\": \"b\"
}"
const url = new URL(
    "https://dev.permbot.net/api/support/tickets/BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc/messages"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "message": "b"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/support/tickets/{ticketUid}/messages

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ticketUid   string     

Example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc

Body Parameters

message   string     

Must not be greater than 10000 characters. Example: b

Support tickets (public guest)

Privacy-scoped endpoints for visitors without an account (e.g. KVKK requests). Message history is never exposed on these routes.

POST api/v1/support/guest

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/v1/support/guest" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"guest_name\": \"b\",
    \"guest_email\": \"zbailey@example.net\",
    \"subject\": \"i\",
    \"message\": \"y\"
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/support/guest"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "guest_name": "b",
    "guest_email": "zbailey@example.net",
    "subject": "i",
    "message": "y"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/support/guest

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

guest_name   string     

Must not be greater than 255 characters. Example: b

guest_email   string     

Must be a valid email address. Must not be greater than 255 characters. Example: zbailey@example.net

locale   string  optional    
subject   string     

Must not be greater than 255 characters. Example: i

message   string     

Must not be greater than 10000 characters. Example: y

GET api/v1/support/guest/reply

requires authentication

Example request:
curl --request GET \
    --get "https://dev.permbot.net/api/v1/support/guest/reply" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"ticket_uid\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
    \"token\": \"g\"
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/support/guest/reply"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "ticket_uid": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
    "token": "g"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (422):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 20
x-ratelimit-remaining: 11
access-control-allow-origin: *
 

{
    "message": "The Token field must be at least 32 characters.",
    "errors": {
        "token": [
            "The Token field must be at least 32 characters."
        ]
    }
}
 

Request      

GET api/v1/support/guest/reply

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

ticket_uid   string     

Must be a valid UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

token   string     

Must be at least 32 characters. Must not be greater than 128 characters. Example: g

POST api/v1/support/guest/reply

requires authentication

Example request:
curl --request POST \
    "https://dev.permbot.net/api/v1/support/guest/reply" \
    --header "Authorization: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"ticket_uid\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
    \"token\": \"g\",
    \"message\": \"z\"
}"
const url = new URL(
    "https://dev.permbot.net/api/v1/support/guest/reply"
);

const headers = {
    "Authorization": "Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "ticket_uid": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
    "token": "g",
    "message": "z"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/support/guest/reply

Headers

Authorization        

Example: Bearer {SANCTUM_PERSONAL_ACCESS_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

ticket_uid   string     

Must be a valid UUID. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

token   string     

Must be at least 32 characters. Must not be greater than 128 characters. Example: g

message   string     

Must not be greater than 10000 characters. Example: z