API Documentation

API address

All methods are available at https://2index.ninja/api/v1/.

Authorization

Pass a Bearer access token in the Authorization header with every request. You can obtain the token in the corresponding section of your account. The token does not expire; generating a new token revokes the old one. API access is not included in every plan: if your plan does not include it, the API returns a 401 error.

Accept header

Send the Accept: application/json header with every request. Without it, authorization errors are returned as a 302 redirect to the login page instead of JSON, and 404, 429 and 500 errors are returned as HTML pages.

curl https://2index.ninja/api/v1/account \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Request format

POST parameters can be sent as a form (application/x-www-form-urlencoded or multipart/form-data) or as JSON (with the Content-Type: application/json header). For boolean parameters (google, yandex, bing, watch, etc.) send 1 or 0. The strings "true" and "false" in a form are not recognized by every method.

Response format

Almost every response contains the success field (true or false) that tells whether the request succeeded. On error, the errors field contains the error messages. Depending on the method, errors is:

  • an array of strings: ["Project not found"];
  • an object "field → list of messages" (validation error): {"links": ["The links field is required."]};
  • in some methods, a different format (a string, an array with an object, etc.); this is noted in the method description.

Validation and business-logic errors are returned with HTTP status 200, so always check success. Error messages are in English.

Response codes

Code When it is returned Response body
200 The request was processed, including validation or business-logic errors {"success": true, ...} or {"success": false, "errors": ...}
401 The token is missing or invalid {"error": "Unauthenticated."}
401 The user's plan does not include API access {"success": false, "errors": ["Forbidden"]}
403 The account is banned {"success": false, "errors": ["Forbidden"]}
404 Unknown method address, or a non-existent project ID in the address (project/{project_id}/clear_queue, link/resent_notindexed_links/{project_id}, link/delete/{project_id}) {"message": "..."}
422 Not used: validation errors are returned with status 200 —
429 Rate limit exceeded {"message": "Too Many Attempts."}
500 Internal server error {"message": "Server Error"}

Rate limit

No more than 300 requests per minute per user (per IP address for the register method, which is called without a token). When the limit is exceeded, the API returns 429; the Retry-After header contains the number of seconds until the limit resets, and the X-RateLimit-Limit and X-RateLimit-Remaining headers contain the limit and the remaining requests.

403 error and User-Agent

If any request returns 403 without a JSON body {"success": false, ...}, the request was most likely blocked by the hosting protection. In this case, specify a User-Agent, for example:

curl https://2index.ninja/api/v1/account \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"

Proxy

Requests from some hosting providers may be restricted at the network level. In this case, we recommend sending requests through a proxy.

Account methods

Register a user

POST https://2index.ninja/api/v1/register

Creates a new account. This method does not require a token. The password is generated automatically.

Request parameters

Field Type Required Description
email string yes User email in the format [email protected]
source string no Registration source, api by default. Can be, for example, wordpress_plugin.

Request example

curl -X POST https://2index.ninja/api/v1/register \
  -H "Accept: application/json" \
  -d "[email protected]"

Successful registration response

{
    "success": true,
    "message": "User registered successfully!",
    "account": {
        "email": "[email protected]",
        "tariff": "White Ninja",
        "available_projects": 1,
        "balance": null,
        "available_links": 0,
        "available_indexation_check_links": 0,
        "link_sending_speed": 100,
        "available_link_sending_speed": 100,
        "tariff_available": false,
        "tariff_expiring_date": null,
        "email_verified": false,
        "link_cost": "$0.00056",
        "api_key": "15|Xq3vT9kLm2Rp8sWz4yB6nC1dF7gH5jK0aE2uI9oP"
    }
}

The api_key field contains the API access token. An email with the password, the token, an email confirmation link and a Telegram account confirmation link will be sent to the specified address.

The free plan is not activated immediately when registering via the API: tokens, limits and the plan expiry date are granted after the email and Telegram are confirmed. Until then, balance is null, tariff_available is false, tariff_expiring_date is null, and you cannot create a project.

Possible errors

Validation error (the email is missing, has an invalid format or is already registered):

{
    "success": false,
    "errors": {
        "email": ["The email has already been taken."]
    }
}

Get account data

GET https://2index.ninja/api/v1/account

Request example

curl https://2index.ninja/api/v1/account \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Response

{
    "success": true,
    "account": {
        "email": "[email protected]",
        "tariff": "White Ninja",
        "available_projects": 1,
        "balance": "100.00",
        "available_links": 100,
        "available_indexation_check_links": 500,
        "link_sending_speed": 100,
        "available_link_sending_speed": 45,
        "tariff_available": true,
        "tariff_expiring_date": "2025-01-16T14:26:11.000000Z",
        "email_verified": true,
        "link_cost": "$0.00056"
    }
}

Where

Field Description
email User email
tariff Plan name
available_projects How many more projects can be created
balance Token balance (a string with two decimal places)
available_links How many links can be sent for indexing with the current balance
available_indexation_check_links How many links can be sent for indexing check with the current balance
link_sending_speed Daily link sending speed of the plan
available_link_sending_speed Free daily speed not allocated to projects
tariff_available The plan is active and not expired
tariff_expiring_date Plan expiry date (ISO 8601, UTC) or null
email_verified The email is confirmed
link_cost Cost of sending one link on the plan

Project methods

Get the list of projects

GET https://2index.ninja/api/v1/project

Returns the user's projects, except archived ones, from newest to oldest.

Pagination (optional)

By default (without parameters), all non-archived projects of the user are returned — this keeps backward compatibility.

If the page parameter is passed, the response switches to pagination mode and additionally contains the pagination block.

Parameter Type Required Description
page integer no Page number. If not passed, all projects are returned.
per_page integer no Page size. Taken into account only together with page. Default 20, allowed range 1…100 (out-of-range values are clamped to the nearest bound).

Request example

curl "https://2index.ninja/api/v1/project?page=2&per_page=10" \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Response

{
    "success": true,
    "projects": [
        {
            "id": 1,
            "name": "Project name",
            "type": "indexing",
            "website": "https://domain.com/",
            "status": "in progress",
            "created_at": "2024-03-01 14:34:56",
            "links_type": "internal",
            "google_account_access_granted": 0,
            "links_total": 500,
            "links_sending_speed": 400,
            "links_sent_google": 140,
            "links_sent_yandex": 0,
            "links_sent_bing": 140,
            "in_queue": 360,
            "sent_links": 140,
            "indexed": 90,
            "not_indexed": 50,
            "instant_indexing": false,
            "check_links": true,
            "geo_target": {
                "criteria_id": 2643,
                "name": "Russia"
            },
            "download_queue_url": "https://2index.ninja/download_project_report/TOKEN/queue",
            "download_sent_url": "https://2index.ninja/download_project_report/TOKEN/sent",
            "download_indexed_url": "https://2index.ninja/download_project_report/TOKEN/indexed",
            "download_unindexed_url": "https://2index.ninja/download_project_report/TOKEN/unindexed"
        },
        {
            "id": 2,
            "name": "Project 2 name",
            "type": "indexing_check",
            "status": "in progress",
            "created_at": "2024-03-01 14:34:56",
            "links_total": 200,
            "links_checking_speed": 100,
            "in_queue": 100,
            "checked": 100,
            "indexed": 70,
            "not_indexed": 30,
            "geo_target": null,
            "download_queue_url": "https://2index.ninja/download_project_report/TOKEN/queue",
            "download_indexed_url": "https://2index.ninja/download_project_report/TOKEN/indexed",
            "download_unindexed_url": "https://2index.ninja/download_project_report/TOKEN/unindexed",
            "download_all_url": "https://2index.ninja/download_project_report/TOKEN/all",
            "download_checked_url": "https://2index.ninja/download_project_report/TOKEN/checked"
        }
    ]
}

In pagination mode (when page is passed), the response additionally contains the pagination block:

{
    "success": true,
    "projects": [],
    "pagination": {
        "current_page": 2,
        "last_page": 5,
        "per_page": 10,
        "total": 47
    }
}

The set of project fields depends on the project type. The fields are described in the "Get project data" method.

Get project data

GET https://2index.ninja/api/v1/project/{project_id}

Request example

curl https://2index.ninja/api/v1/project/1 \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Response for an indexing project

{
    "success": true,
    "project": {
        "id": 1,
        "name": "Project name",
        "type": "indexing",
        "website": "https://domain.com/",
        "status": "in progress",
        "created_at": "2024-03-01 14:34:56",
        "links_type": "internal",
        "google_account_access_granted": 0,
        "links_total": 500,
        "links_sending_speed": 400,
        "links_sent_google": 140,
        "links_sent_yandex": 0,
        "links_sent_bing": 140,
        "in_queue": 360,
        "sent_links": 140,
        "indexed": 90,
        "not_indexed": 50,
        "instant_indexing": false,
        "check_links": true,
        "geo_target": {
            "criteria_id": 2643,
            "name": "Russia"
        },
        "download_queue_url": "https://2index.ninja/download_project_report/TOKEN/queue",
        "download_sent_url": "https://2index.ninja/download_project_report/TOKEN/sent",
        "download_indexed_url": "https://2index.ninja/download_project_report/TOKEN/indexed",
        "download_unindexed_url": "https://2index.ninja/download_project_report/TOKEN/unindexed"
    }
}

Response for an indexing check project

{
    "success": true,
    "project": {
        "id": 2,
        "name": "Project 2 name",
        "type": "indexing_check",
        "status": "in progress",
        "created_at": "2024-03-01 14:34:56",
        "links_total": 200,
        "links_checking_speed": 100,
        "in_queue": 100,
        "checked": 100,
        "indexed": 70,
        "not_indexed": 30,
        "geo_target": {
            "criteria_id": 2840,
            "name": "United States"
        },
        "download_queue_url": "https://2index.ninja/download_project_report/TOKEN/queue",
        "download_indexed_url": "https://2index.ninja/download_project_report/TOKEN/indexed",
        "download_unindexed_url": "https://2index.ninja/download_project_report/TOKEN/unindexed",
        "download_all_url": "https://2index.ninja/download_project_report/TOKEN/all",
        "download_checked_url": "https://2index.ninja/download_project_report/TOKEN/checked"
    }
}

Where

Field Description
id Project ID
name Project name
type Project type: indexing — indexing, indexing_check — indexing check
website Website address (indexing only)
status Status: in progress, stopped, completed, wait for moderation, archived
created_at Creation date (Y-m-d H:i:s)
links_type Which links the project accepts: internal, external or internal, external (indexing only)
google_account_access_granted Google account access mark, 0 or 1 (indexing only)
links_total Total links in the project
links_sending_speed Daily sending speed (indexing only)
links_checking_speed Daily checking speed (indexing_check only)
links_sent_google, links_sent_yandex, links_sent_bing Links sent to Google, Yandex, Bing (indexing only)
in_queue Links in the queue
sent_links Total links sent (indexing only)
checked Links checked (indexing_check only)
indexed Indexed
not_indexed Not indexed
instant_indexing Google instant indexing (indexing only)
check_links Automatic indexing check of Google links is enabled (indexing only)
geo_target Google search region for the indexing check: criteria_id and name. null — no region set (the United States is used)
download_*_url Links to download project reports

Possible errors

The project does not exist or belongs to another user:

{
    "success": false,
    "errors": ["Project not found"]
}

Create a project

POST https://2index.ninja/api/v1/project

Request parameters

Field Type Required Description
name string yes Project name
website string yes* Website address strictly in the format https://site.domain/: http or https scheme, domain and a trailing slash, no path. An address without the trailing slash (https://site.domain) fails validation.
*Required if type: indexing is passed. If type is not passed, only the format is checked (when present), so we recommend always passing both type and website
type string no Project type: indexing — link indexing, indexing_check — link indexing check.
If not specified, a link indexing project indexing is created
for_external_links boolean no Project for external links (1). If not specified, the project accepts only internal links (from the website domain).
indexing_speed integer no** Daily link sending speed, from 1 to the free speed of the account (available_link_sending_speed). If not specified, the project gets all the free speed of the account. For instant indexing the speed cannot exceed the daily limit of the plan (1 link per day on the free plan); if the speed is not specified, the minimum of the free speed and this limit is used.
checking_speed integer no** Daily link checking speed. If not specified, the project gets all the free speed of the account.
instant_indexing boolean no Google instant indexing. Available only for indexing projects while the service is enabled; otherwise the API returns an error. Google links are sent with the highest priority and are charged at the instant indexing price of your plan; Yandex and Bing are processed as usual.
check_links boolean no Automatic indexing check of Google links for indexing projects. Disabled by default — enabled only with an explicit check_links: 1.
⚠️ When enabled, each Google link costs more: sending + indexing check at your plan's price. Not available for instant indexing projects (instant_indexing) and ignored for them.
geo_target_id integer no Google search region (country) for the indexing check — criteria_id from the regions table or the "Get indexing-check regions" method (for example, 2840 — United States, 2643 — Russia). If not specified — United States.
Applies to indexing_check projects and to indexing projects with check_links enabled; ignored otherwise.
google_account_access_granted boolean no Mark that you have granted access to your Google account. 0 by default.

** indexing_speed and checking_speed are aliases and work for a project of any type. If both are passed, indexing_speed is used.

Request examples

Link indexing project:

curl -X POST https://2index.ninja/api/v1/project \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "name=Project 1" \
  -d "type=indexing" \
  -d "website=https://website.com/" \
  -d "for_external_links=1" \
  -d "indexing_speed=100"

Instant indexing project:

curl -X POST https://2index.ninja/api/v1/project \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "name=Instant Project" \
  -d "type=indexing" \
  -d "website=https://website.com/" \
  -d "instant_indexing=1" \
  -d "indexing_speed=100"

Indexing project with automatic indexing check of Google links (check region — Russia):

curl -X POST https://2index.ninja/api/v1/project \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "name=Project 1" \
  -d "type=indexing" \
  -d "website=https://website.com/" \
  -d "indexing_speed=100" \
  -d "check_links=1" \
  -d "geo_target_id=2643"

Link indexing check project:

curl -X POST https://2index.ninja/api/v1/project \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "name=Project 2" \
  -d "type=indexing_check" \
  -d "checking_speed=100" \
  -d "geo_target_id=2840"

Response

The project field contains the created project in the same format as in the "Get project data" method:

{
    "success": true,
    "message": "The project has been successfully created",
    "project": {
        "id": 3,
        "name": "Project 2",
        "type": "indexing_check",
        "status": "in progress",
        "created_at": "2024-03-01 14:34:56",
        "links_total": 0,
        "links_checking_speed": 100,
        "in_queue": 0,
        "checked": 0,
        "indexed": 0,
        "not_indexed": 0,
        "geo_target": {
            "criteria_id": 2840,
            "name": "United States"
        },
        "download_queue_url": "https://2index.ninja/download_project_report/TOKEN/queue",
        "download_indexed_url": "https://2index.ninja/download_project_report/TOKEN/indexed",
        "download_unindexed_url": "https://2index.ninja/download_project_report/TOKEN/unindexed",
        "download_all_url": "https://2index.ninja/download_project_report/TOKEN/all",
        "download_checked_url": "https://2index.ninja/download_project_report/TOKEN/checked"
    }
}

Possible errors

Validation error. Note that here errors is an array with a single "field → list of messages" object:

{
    "success": false,
    "errors": [
        {
            "website": ["The website field must be a valid URL. For example https://site.domain/"]
        }
    ]
}

Other errors are returned as an array of strings, for example:

{
    "success": false,
    "errors": ["The limit of available projects has been reached"]
}
Error Reason
You must confirm your email The email is not confirmed
The limit of available projects has been reached The plan's project limit has been reached
You cannot create the project. Your tariff has expired The plan is inactive or expired
You must confirm your telegram account The Telegram account is not confirmed on the free plan
Unable to create the project. Your daily sending speed is fully allocated to other projects. ... No speed was specified, and all the account speed is already allocated to other projects
Instant indexing is available only for indexing projects. instant_indexing was passed for an indexing_check project (in the validation object)
Instant indexing is currently unavailable. The instant indexing service is currently disabled (in the validation object)
Your tariff limits instant indexing speed to N per day. The instant indexing speed exceeds the plan limit (in the validation object)

Get indexing-check regions

GET https://2index.ninja/api/v1/geo-targets

Returns the list of Google search regions (countries), sorted by name. Pass criteria_id in the geo_target_id parameter when creating a project. A regular page with the regions table is available at https://2index.ninja/en/geo-targets.

Request example

curl https://2index.ninja/api/v1/geo-targets \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Response

{
    "success": true,
    "geo_targets": [
        {
            "criteria_id": 2643,
            "name": "Russia",
            "country_code": "ru"
        },
        {
            "criteria_id": 2840,
            "name": "United States",
            "country_code": "us"
        }
    ]
}

Create (or get) a project for the WordPress plugin

POST https://2index.ninja/api/v1/account/get_wordpress_project

Finds the indexing project linked to a WordPress website. If there is no such project, creates a new one.

Request parameters

Field Type Required Description
website string yes Website address. It is normalized to https://site.domain/: the scheme defaults to https, the domain is lowercased, and the path is dropped. For example, Site.domain/blog becomes https://site.domain/.

The method looks for a non-archived user project for internal links with the same website address. If none is found, it creates an indexing project for internal links named after the domain, with all the free sending speed of the account.

Request example

curl -X POST https://2index.ninja/api/v1/account/get_wordpress_project \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "website=https://domain.com/"

Response for an existing project

{
    "success": true,
    "message": "Found existing project domain.com",
    "project": {
        "id": 1,
        "name": "domain.com",
        "type": "indexing",
        "website": "https://domain.com/",
        "status": "in progress",
        "created_at": "2024-03-01 14:34:56",
        "links_type": "internal",
        "google_account_access_granted": 0,
        "links_total": 500,
        "links_sending_speed": 400,
        "links_sent_google": 140,
        "links_sent_yandex": 0,
        "links_sent_bing": 140,
        "in_queue": 360,
        "sent_links": 140,
        "indexed": 90,
        "not_indexed": 50,
        "instant_indexing": false,
        "check_links": false,
        "geo_target": null,
        "download_queue_url": "https://2index.ninja/download_project_report/TOKEN/queue",
        "download_sent_url": "https://2index.ninja/download_project_report/TOKEN/sent",
        "download_indexed_url": "https://2index.ninja/download_project_report/TOKEN/indexed",
        "download_unindexed_url": "https://2index.ninja/download_project_report/TOKEN/unindexed"
    }
}

Response for a new project

{
    "success": true,
    "message": "The project has been successfully created",
    "project": {
        "id": 4,
        "name": "domain.com",
        "type": "indexing",
        "website": "https://domain.com/",
        "status": "in progress",
        "created_at": "2024-03-01 14:34:56",
        "links_type": "internal",
        "google_account_access_granted": 0,
        "links_total": 0,
        "links_sending_speed": 45,
        "links_sent_google": 0,
        "links_sent_yandex": 0,
        "links_sent_bing": 0,
        "in_queue": 0,
        "sent_links": 0,
        "indexed": 0,
        "not_indexed": 0,
        "instant_indexing": false,
        "check_links": false,
        "geo_target": null,
        "download_queue_url": "https://2index.ninja/download_project_report/TOKEN/queue",
        "download_sent_url": "https://2index.ninja/download_project_report/TOKEN/sent",
        "download_indexed_url": "https://2index.ninja/download_project_report/TOKEN/indexed",
        "download_unindexed_url": "https://2index.ninja/download_project_report/TOKEN/unindexed"
    }
}

Possible errors

A validation error is returned without the success and errors fields, as a "field → list of messages" object:

{
    "website": ["The website field must be a valid URL. For example https://site.domain/"]
}

When a new project is created, the same errors as in the "Create a project" method are possible (You must confirm your email, The limit of available projects has been reached, etc.).

Clear the indexing queue

POST https://2index.ninja/api/v1/project/{project_id}/clear_queue

Removes links that have not been sent yet (queued) from the project. Tokens for the removed links are returned to the balance. Works for projects of both types.

Request example

curl -X POST https://2index.ninja/api/v1/project/1/clear_queue \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Response

{
    "success": true,
    "message": "The indexing queue has been cleared successfully. 360 links have been removed"
}

Possible errors

The project belongs to another user:

{
    "success": false,
    "errors": ["Project not found"]
}

The project is deeply archived:

{
    "success": false,
    "errors": ["You cannot clear queue. The project is deeply archived"]
}

If there is no project with this ID, HTTP status 404 is returned.

Reset speed of completed projects

POST https://2index.ninja/api/v1/project/reset_speed_completed

A bulk operation — sets the speed to 0 for all completed (completed) projects of the user. Useful when, after many projects are completed, you need to free up the allocated speed without archiving them.

Request parameters

Without parameters, all completed projects of the user with a speed greater than 0 are affected. All filter parameters are optional; dates are passed in the Y-m-d format, range bounds are inclusive.

Field Type Required Description
created_at_from string no Project creation date, lower bound (Y-m-d).
created_at_to string no Project creation date, upper bound (Y-m-d).
completed_at_from string no Date of transition to completed, lower bound.
completed_at_to string no Date of transition to completed, upper bound.

Request example

curl -X POST https://2index.ninja/api/v1/project/reset_speed_completed \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "completed_at_from=2025-01-01" \
  -d "completed_at_to=2025-01-31"

Response

{
    "success": true,
    "message": "Speed reset for 5 projects",
    "affected_projects": 5
}

Possible errors

Invalid date format. Here errors is an array with a single "field → list of messages" object:

{
    "success": false,
    "errors": [
        {
            "completed_at_from": ["The completed at from field must match the format Y-m-d."]
        }
    ]
}

Archive completed projects

POST https://2index.ninja/api/v1/project/archive_completed

A bulk operation — moves all completed (completed) projects of the user to the archived status and resets their speed. Useful for closing completed projects in bulk without per-project requests.

Request parameters

Accepts the same optional date range filters as "Reset speed of completed projects":

Field Type Required Description
created_at_from string no Project creation date, lower bound (Y-m-d).
created_at_to string no Project creation date, upper bound (Y-m-d).
completed_at_from string no Date of transition to completed, lower bound.
completed_at_to string no Date of transition to completed, upper bound.

Request example

curl -X POST https://2index.ninja/api/v1/project/archive_completed \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Response

{
    "success": true,
    "message": "5 projects have been archived",
    "archived_count": 5
}

Possible errors

Invalid date format — in the same format as in the "Reset speed of completed projects" method.

Link methods

Add links

POST https://2index.ninja/api/v1/link/add

Adds links to an existing project. For indexing check projects, links are added the same way as for indexing, but the check is currently available only in Google — pass only google=1.

Request parameters

Field Required Description
project_id yes Project ID
links yes List of links: an array (links[]=...) or text with each link on a new line. Surrounding spaces and duplicates are removed
google no, if yandex or bing is specified Send links to Google (1 or 0)
yandex no, if google or bing is specified Send links to Yandex (1 or 0)
bing no, if google or yandex is specified Send links to Bing (1 or 0)

At least one of the google, yandex, bing parameters must be 1. The google_access_granted parameter mentioned in previous versions of the documentation is not used by this method.

How links are processed

  • Strings that are not valid URLs (http:// or https://, no spaces, etc.) are not added and do not cause an error: they are returned in the invalid_links field of the successful response.
  • If some valid URLs are longer than 3000 characters or are not from the project domain (for an internal-links-only project, or if your plan does not allow external links), no links are added and an error is returned.
  • If the token balance is not enough for all the links, the first links of the list that the balance covers are added; the response is still successful.

Request example

curl -X POST https://2index.ninja/api/v1/link/add \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "project_id=1" \
  -d "links[]=https://domain.com/page1" \
  -d "links[]=https://domain.com/page2" \
  -d "google=1" \
  -d "bing=1"

Response

The message of a successful response always contains the phrase Invalid links was ignored., and the invalid_links field is always present — an empty array if there were no invalid links:

{
    "success": true,
    "message": "Links have been successfully added to the project. Invalid links was ignored.",
    "invalid_links": ["not a link"],
    "project_id": 1
}

Possible errors

The links contain external or too long links. The error text contains <br> HTML tags:

{
    "success": false,
    "errors": ["You sent invalid links. <br> You can only add internal links to this project<br><br>Invalid links:<br>https://other-site.com/page"],
    "invalid_links": ["https://other-site.com/page"]
}

Validation error (errors is a "field → list of messages" object):

{
    "success": false,
    "errors": {
        "google": ["At least one of Google, Yandex, or Bing must be selected."]
    }
}

Other errors are returned as an array of strings:

Error Reason
Project not found project_id is missing, the project does not exist or belongs to another user
You cannot add links to the project. The project is deeply archived The project is deeply archived
You cannot add links to the project. Your tariff has expired. The plan is inactive or expired
You do not have enough tokens to add any links. You need at least N tokens. The balance is not enough for a single link
To check indexing, you can only select Google. Not only Google was selected for an indexing check project (in the validation object)
Adding links to Yandex is temporarily unavailable. Adding links to this search engine is temporarily disabled (in the validation object)
An error occurred while adding links: ... The links could not be added, for example: Instant indexing is temporarily unavailable. You cannot add links to this project.

Add links by project name

POST https://2index.ninja/api/v1/link/add_simple

Adds links to a project found by name. The method looks for a user project with the in progress, stopped or completed status. If there is no such project, an indexing project without a website address is created automatically, for internal and external links, with all the free sending speed of the account.

Request parameters

Field Required Description
project_name no Project name. If not specified, the project named default is used.
links yes List of links: an array or text with each link on a new line.
google no, if yandex or bing is specified Send links to Google (1 or 0).
yandex no, if google or bing is specified Send links to Yandex (1 or 0).
bing no, if google or yandex is specified Send links to Bing (1 or 0).

At least one of the google, yandex, bing parameters must be 1. If the found project is an indexing check project, pass only google=1. Links are processed the same way as in the "Add links" method.

Request example

curl -X POST https://2index.ninja/api/v1/link/add_simple \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "My Website",
    "links": ["https://example.com/page1", "https://example.com/page2"],
    "google": 1,
    "yandex": 0,
    "bing": 1
  }'

Response

{
    "success": true,
    "message": "Links have been successfully added to the project. Invalid links was ignored.",
    "invalid_links": [],
    "project_id": 12345
}

Possible errors

Link processing and balance errors are the same as in the "Add links" method. A validation error is returned as a "field → list of messages" object:

{
    "success": false,
    "errors": {
        "links": ["The links field is required."]
    }
}

Errors when creating a new project (an array of strings):

Error Reason
Unable to create a new project. The limit of available projects has been reached. Please specify the name of an existing project. The plan's project limit has been reached
You must confirm your email The email is not confirmed
You cannot create the project. Your tariff has expired The plan is inactive or expired
You must confirm your telegram account The Telegram account is not confirmed on the free plan
Unable to create a new project. Your daily sending speed is fully allocated to other projects. ... All the account speed is already allocated to other projects

If the project is found but the plan is inactive: You cannot add links to the project. Your tariff has expired.

Check link status

POST https://2index.ninja/api/v1/link/status

Returns the status of a link in an indexing project. If the link was added several times, the latest record is returned. For indexing check projects, the method always returns URL not found.

Request parameters

Field Required Description
project_id yes Project ID
link yes The link to check (exact match)

Request example

curl -X POST https://2index.ninja/api/v1/link/status \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "project_id=1" \
  -d "link=https://2index.ninja/"

Response

{
    "success": true,
    "link": {
        "id": 34368960,
        "url": "https://2index.ninja/",
        "google": "Sent",
        "yandex": "Sent",
        "bing": "Sent",
        "is_external": 1,
        "google_sent": "2025-08-24 20:10:02",
        "yandex_sent": "2025-08-24 20:45:02",
        "bing_sent": "2025-08-24 20:05:05",
        "google_sent_attempts": 1,
        "yandex_sent_attempts": 1,
        "bing_sent_attempts": 1,
        "google_indexed": 1,
        "google_indexing_check_date": "2025-08-29"
    }
}

Where

Field Description
id Link ID
url Link
google * Google sending status
yandex * Yandex sending status
bing * Bing sending status
is_external External link (0 or 1)
google_sent Date sent to Google
yandex_sent Date sent to Yandex
bing_sent Date sent to Bing
google_sent_attempts Number of submissions to Google
yandex_sent_attempts Number of submissions to Yandex
bing_sent_attempts Number of submissions to Bing
google_indexed Indexed in Google (0 or 1)
google_indexing_check_date Google indexing check date

* Possible statuses

Status Meaning
New New
Sent Sent
In the queue In the sending queue
In the process Being sent
Error sending An error occurred while sending
Don't index Not sent to this search engine
Sent to reindexing Sent for reindexing

Possible errors

{
    "success": false,
    "errors": {
        "link": ["The link field is required."]
    }
}
{
    "success": false,
    "errors": ["Project not found"]
}
{
    "success": false,
    "errors": ["URL not found"]
}

Get link status by ID

GET https://2index.ninja/api/v1/link/status/{link_id}

Returns the status of a link by its ID (the id field from the "Check link status" method). Works only for links of indexing projects.

Request example

curl https://2index.ninja/api/v1/link/status/34368960 \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json"

Response

The same as in the "Check link status" method.

Possible errors

No link with this ID — {"success": false, "errors": ["URL not found"]}; the link belongs to another user's project — {"success": false, "errors": ["Project not found"]}.

Resend links for reindexing

POST https://2index.ninja/api/v1/link/resent_notindexed_links/{project_id}

Resends non-indexed links of an indexing project to Google. The project ID is passed in the address.

If links is not passed, the method takes all project links that were sent to Google, checked for indexing, not indexed, and sent fewer than max_attempts times. If links is passed, the listed links sent fewer than max_attempts times are resent; links that have never been sent are added to the queue too. Links are added according to the balance, as in the "Add links" method, and the previous records of these links get the Sent to reindexing status.

Request parameters

Field Required Description
max_attempts yes Limit of link submissions, an integer from 1 to 10. Links sent max_attempts times or more are skipped
links no Array of links to resend. If not specified, all suitable non-indexed links of the project are used

Request example

curl -X POST https://2index.ninja/api/v1/link/resent_notindexed_links/1 \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "max_attempts=3"

Response

{
    "success": true,
    "message": "Links have been successfully added to the project. Invalid links was ignored.",
    "invalid_links": [],
    "project_id": 1,
    "ignored_links": [
        {
            "url": "https://domain.com/url1",
            "attempts": 3
        },
        {
            "url": "https://domain.com/url2",
            "attempts": 4
        }
    ]
}

Where

Field Description
success Request success status
message Success message
invalid_links Invalid links that were not added
project_id Project ID
ignored_links Skipped links that were already sent max_attempts times or more, with the number of submissions

Possible errors

Validation error:

{
    "success": false,
    "errors": {
        "max_attempts": ["The max attempts field must not be greater than 10."],
        "links": ["The links field must be an array."]
    }
}

No links to resend. Note that the text is returned in the message field, not in errors:

{
    "success": false,
    "message": "No links available for reindexing"
}

Not enough tokens (the ignored_links field is present in the error response too):

{
    "success": false,
    "errors": ["You do not have enough tokens to add any links. You need at least 1 tokens."],
    "ignored_links": []
}

The project belongs to another user. Here errors is a string:

{
    "success": false,
    "errors": "Project not found"
}

The project is deeply archived — {"success": false, "errors": ["You cannot work with links. The project is deeply archived"]}. If there is no project with this ID, HTTP status 404 is returned.

Delete links from a project

POST https://2index.ninja/api/v1/link/delete/{project_id}

Deletes a link or an array of links from a project (all records with this address). The project ID is passed in the address. If a link is still in the sending queue, its tokens are returned to the balance. Links that have already been sent are deleted without a refund.

Request parameters

Field Required Description
link yes The link to delete: a single string or an array of links (link[]=...).

Request example

curl -X POST https://2index.ninja/api/v1/link/delete/1 \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "link[]=https://domain.com/page1" \
  -d "link[]=https://domain.com/page2"

Response

{
    "success": true,
    "deleted": 2,
    "message": "The URLs have been deleted."
}

If the project contained several identical URLs (for example, a link was re-submitted), the response has an additional duplicates field:

{
    "success": true,
    "deleted": 5,
    "duplicates": 3,
    "message": "The URLs have been deleted, including duplicate (re-submitted) entries."
}

If none of the passed links were found in the project:

{
    "success": true,
    "deleted": 0,
    "message": "No URLs were found to delete."
}

Possible errors

{
    "success": false,
    "errors": {
        "link": ["The link field is required."]
    }
}
{
    "success": false,
    "errors": ["You cannot work with links. The project is deeply archived"]
}

The project belongs to another user. Here errors is a string:

{
    "success": false,
    "errors": "Project not found"
}

If there is no project with this ID, HTTP status 404 is returned.

Link source methods

Link sources are sitemaps and text files from which links are loaded into a project. They are available for projects of any type and are processed the same way. For indexing check projects, the check is performed only in Google, so pass only google=1.

In all methods of this section, errors are returned in the {"success": false, "errors": ["..."]} format. Common errors:

Error Reason
Project not found project_id is missing, the project does not exist or belongs to another user
You cannot work with the link source. The project is deeply archived The project is deeply archived

Add a sitemap

POST https://2index.ninja/api/v1/sitemap/add

Adds a sitemap to a project. The sitemap is downloaded in the background; you will receive an email notification when it is processed. The processing state is available via the "Get the list of link sources" method.

Request parameters

Field Required Description
project_id yes Project ID
sitemap yes Sitemap URL
google no, if yandex or bing is specified Send links to Google (1 or 0)
yandex no, if google or bing is specified Send links to Yandex (1 or 0)
bing no, if google or yandex is specified Send links to Bing (1 or 0)
is_external no Links from the sitemap are external (1). 0 by default
google_access_granted no Google account access mark (1 or 0)
watch no Watch the sitemap for changes (1 or 0). 0 by default

Request example

curl -X POST https://2index.ninja/api/v1/sitemap/add \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "project_id=1" \
  -d "sitemap=https://domain.com/sitemap.xml" \
  -d "google=1" \
  -d "watch=1"

Response

The sitemap field contains the created link source in the format of the "Get the list of link sources" method:

{
    "success": true,
    "message": "The sitemap has been successfully added, we will download it as quickly as possible, and upon completion we will send an email.",
    "sitemap": {
        "id": 1248,
        "project_id": 1,
        "name": "https://domain.com/sitemap.xml",
        "type": "sitemap",
        "created_at": "2025-01-17T16:02:11.000000Z",
        "processing_date": null,
        "has_error": false,
        "error_message": null,
        "status": "pending",
        "is_pending": true,
        "is_success": false,
        "total_links": null,
        "added_links": null,
        "invalid_links": null,
        "watch": true,
        "google_access_granted": false,
        "is_external_links": false,
        "search_engines": {
            "google": 1
        }
    }
}

Possible errors

Validation error. Note that the error object is returned as a JSON string inside the errors array:

{
    "success": false,
    "errors": ["{\"sitemap\":[\"The sitemap field is required.\"]}"]
}

The plan is inactive or expired:

{
    "success": false,
    "errors": ["You cannot add links to the project. Your tariff has expired."]
}

If adding links to the selected search engine is temporarily disabled, the validation string contains a message like Adding links to Yandex is temporarily unavailable.

Update sitemap watching

POST https://2index.ninja/api/v1/sitemap/update_watch

Enables or disables watching a sitemap for changes.

Request parameters

Field Type Required Description
project_id integer yes Project ID
sitemap_id integer yes Sitemap ID (the id field of the link source)
watch boolean no 1 — watch for changes, 0 — do not watch. If the parameter is not passed, watching is disabled

Request example

curl -X POST https://2index.ninja/api/v1/sitemap/update_watch \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "project_id=1" \
  -d "sitemap_id=1248" \
  -d "watch=0"

Response

{
    "success": true
}

Possible errors

No source with this ID in the project:

{
    "success": false,
    "errors": ["Sitemap not found"]
}

The link source is not a sitemap (for example, a text file):

{
    "success": false,
    "errors": ["Wrong link source type"]
}

Delete a link source

POST https://2index.ninja/api/v1/sitemap/delete

Deletes a link source from a project — a sitemap or a text file.

Request parameters

Field Type Required Description
project_id integer yes Project ID
sitemap_id integer yes Link source ID (the id field from the "Get the list of link sources" method)

Request example

curl -X POST https://2index.ninja/api/v1/sitemap/delete \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "project_id=1" \
  -d "sitemap_id=1248"

Response

The message is the same for sitemaps and text files:

{
    "success": true,
    "message": "The sitemap has been successfully deleted"
}

Possible errors

No source with this ID in the project:

{
    "success": false,
    "errors": ["Sitemap not found"]
}

Get the list of link sources

POST https://2index.ninja/api/v1/link_sources

Returns the link sources of a project (sitemaps and text files), from newest to oldest.

Request parameters

Field Type Required Description
project_id integer yes Project ID

Request example

curl -X POST https://2index.ninja/api/v1/link_sources \
  -H "Authorization: Bearer API_TOKEN" \
  -H "Accept: application/json" \
  -d "project_id=3540"

Response

On success, an array of sources is returned without the success field:

[
    {
        "id": 1247,
        "project_id": 3540,
        "name": "https://domain.com/sitemap.xml",
        "type": "sitemap",
        "created_at": "2025-01-17T15:51:43.000000Z",
        "processing_date": "2025-01-17T15:51:48.000000Z",
        "has_error": false,
        "error_message": "",
        "status": "success",
        "is_pending": false,
        "is_success": true,
        "total_links": 29585,
        "added_links": 29582,
        "invalid_links": 3,
        "watch": 1,
        "google_access_granted": 0,
        "is_external_links": 0,
        "search_engines": {
            "google": 1
        }
    },
    {
        "id": 1245,
        "project_id": 3540,
        "name": "urls.txt",
        "type": "text file",
        "created_at": "2025-01-17T15:46:17.000000Z",
        "processing_date": null,
        "has_error": false,
        "error_message": null,
        "status": "pending",
        "is_pending": true,
        "is_success": false,
        "total_links": 0,
        "added_links": 0,
        "invalid_links": 0,
        "watch": 0,
        "google_access_granted": 0,
        "is_external_links": 1,
        "search_engines": {
            "google": "1",
            "bing": "1"
        }
    }
]

Where

Field Description
id Link source ID
project_id Project ID
name Sitemap address or uploaded file name
type Source type: sitemap or text file
created_at Creation date (ISO 8601, UTC)
processing_date Processing date or null
has_error A processing error occurred
error_message Error message
status Status: pending (waiting for processing), error (processing error), success (processed)
is_pending Waiting for processing
is_success Processed successfully
total_links Total links found
added_links Links added
invalid_links Number of invalid links
watch Watching the sitemap for changes (0 or 1)
google_access_granted Google account access mark (0 or 1)
is_external_links Links are marked as external (0 or 1)
search_engines Selected search engines

Possible errors

{
    "success": false,
    "errors": ["Project not found"]
}

PHP implementation example

<?php

/**
 * Class for working with the 2Index.Ninja API
 */
class API_2IndexNinja
{
    /**
     * API endpoint URL
     * @var string
     */
    private $url = 'https://2index.ninja/api/v1/';

    /**
     * API access token
     * @var string
     */
    private $access_token;

    /**
     * Class constructor.
     *
     * @param string $access_token Access token for API authorization.
     */
    public function __construct($access_token)
    {
        $this->access_token = $access_token;
    }

    /*
    |--------------------------------------------------------------------------
    | Account methods
    |--------------------------------------------------------------------------
    */

    /**
     * Gets information about the current account.
     *
     * @return array|null Decoded API response with account data.
     */
    public function account()
    {
        return $this->make_request('account');
    }

    /**
     * Registers a new user. This method does not require a token.
     *
     * @param string $email New user's email.
     * @param string $source Registration source.
     * @return array|null Decoded API response (the token is in account.api_key).
     */
    public function register_user($email, $source = 'api')
    {
        return $this->make_request('register', [
            'email'  => $email,
            'source' => $source,
        ]);
    }

    /*
    |--------------------------------------------------------------------------
    | Project methods
    |--------------------------------------------------------------------------
    */

    /**
     * Gets the list of the user's projects.
     *
     * Without parameters, returns all non-archived projects. If $page is passed,
     * pagination is enabled and the response contains the 'pagination' block.
     *
     * @param int|null $page     Page number (if specified, pagination is enabled).
     * @param int|null $per_page Page size (1..100, 20 by default). Used only with $page.
     * @return array|null Decoded API response with the list of projects.
     */
    public function projects_list($page = null, $per_page = null)
    {
        $url_data = [];
        if ($page) {
            $url_data['page'] = $page;

            if ($per_page) {
                $url_data['per_page'] = $per_page;
            }
        }

        $url_data = $url_data ? ('?' . http_build_query($url_data)) : '';

        return $this->make_request('project' . $url_data);
    }

    /**
     * Gets the data of a specific project by its ID.
     *
     * @param int $id Project ID.
     * @return array|null Decoded API response with the project data.
     */
    public function project($id)
    {
        return $this->make_request('project/' . $id);
    }

    /**
     * Adds a new link indexing project.
     *
     * @param string $name Project name.
     * @param string $website Project website in the format https://site.domain/ (with a trailing slash).
     * @param bool $for_external_links Whether the project is for external links (0 or 1).
     * @param int|null $indexing_speed Indexing speed.
     * @return array|null Decoded API response with the created project data.
     */
    public function add_project($name, $website, $for_external_links = 0, $indexing_speed = null)
    {
        return $this->make_request('project', [
            'name'               => $name,
            'website'            => $website,
            'for_external_links' => $for_external_links,
            'indexing_speed'     => $indexing_speed,
            'type'               => 'indexing',
        ]);
    }

    /**
     * Adds a new indexing check project.
     *
     * @param string $name Project name.
     * @param int|null $checking_speed Checking speed.
     * @param int|null $geo_target_id Google search region (criteria_id from geo_targets()).
     * @return array|null Decoded API response with the created project data.
     */
    public function add_indexing_check_project($name, $checking_speed = null, $geo_target_id = null)
    {
        return $this->make_request('project', [
            'name'           => $name,
            'checking_speed' => $checking_speed,
            'geo_target_id'  => $geo_target_id,
            'type'           => 'indexing_check',
        ]);
    }

    /**
     * Gets the list of Google search regions for the indexing check.
     *
     * @return array|null Decoded API response.
     */
    public function geo_targets()
    {
        return $this->make_request('geo-targets');
    }

    /**
     * Finds the project linked to a WordPress website.
     * If there is no such project, creates a new one.
     *
     * @param string $website Website URL to look up the project by.
     * @return array|null Decoded API response with the project data.
     */
    public function get_wordpress_project($website)
    {
        return $this->make_request('account/get_wordpress_project', ['website' => $website]);
    }

    /**
     * Clears the link queue of a project.
     *
     * @param int $project_id Project ID.
     * @return array|null Decoded API response.
     */
    public function clear_queue($project_id)
    {
        return $this->make_request('project/' . $project_id . '/clear_queue', true);
    }

    /**
     * Resets the indexing speed of all completed projects of the user.
     * All filter parameters are optional; dates in the Y-m-d format.
     *
     * @param string|null $created_at_from
     * @param string|null $created_at_to
     * @param string|null $completed_at_from
     * @param string|null $completed_at_to
     * @return array|null Decoded response: success, message, affected_projects.
     */
    public function reset_speed_completed(
        $created_at_from = null,
        $created_at_to = null,
        $completed_at_from = null,
        $completed_at_to = null
    ) {
        $payload = array_filter([
            'created_at_from'   => $created_at_from,
            'created_at_to'     => $created_at_to,
            'completed_at_from' => $completed_at_from,
            'completed_at_to'   => $completed_at_to,
        ]);

        // Without parameters, make_request() treats an empty array as "no POST
        // data" and sends a GET, which is caught by the project/{id} route.
        // So with empty filters we force a POST by passing true.
        return $this->make_request('project/reset_speed_completed', $payload ?: true);
    }

    /**
     * Archives all completed projects of the user.
     * Accepts the same optional date range filters as reset_speed_completed.
     *
     * @param string|null $created_at_from
     * @param string|null $created_at_to
     * @param string|null $completed_at_from
     * @param string|null $completed_at_to
     * @return array|null Decoded response: success, message, archived_count.
     */
    public function archive_completed(
        $created_at_from = null,
        $created_at_to = null,
        $completed_at_from = null,
        $completed_at_to = null
    ) {
        $payload = array_filter([
            'created_at_from'   => $created_at_from,
            'created_at_to'     => $created_at_to,
            'completed_at_from' => $completed_at_from,
            'completed_at_to'   => $completed_at_to,
        ]);

        return $this->make_request('project/archive_completed', $payload ?: true);
    }

    /*
    |--------------------------------------------------------------------------
    | Link methods
    |--------------------------------------------------------------------------
    */

    /**
     * Adds links to the specified project.
     *
     * @param int $project_id Project ID.
     * @param array|string $links Array of links or text (each link on a new line).
     * @param bool $google Whether to send to Google.
     * @param bool $yandex Whether to send to Yandex.
     * @param bool $bing Whether to send to Bing.
     * @return array|null Decoded API response.
     */
    public function add_links($project_id, $links, $google, $yandex, $bing)
    {
        return $this->make_request('link/add', [
            'project_id' => $project_id,
            'links'      => $links,
            'google'     => (int) $google,
            'yandex'     => (int) $yandex,
            'bing'       => (int) $bing,
        ]);
    }

    /**
     * Adds links to a project by name. If the project does not exist, it is created.
     *
     * @param array|string $links Array of links or text (each link on a new line).
     * @param bool $google Whether to send to Google.
     * @param bool $yandex Whether to send to Yandex.
     * @param bool $bing Whether to send to Bing.
     * @param string $project_name Project name ('default' by default).
     * @return array|null Decoded API response.
     */
    public function add_links_simple($links, $google, $yandex, $bing, $project_name = 'default')
    {
        return $this->make_request('link/add_simple', [
            'project_name' => $project_name,
            'links'        => $links,
            'google'       => (int) $google,
            'yandex'       => (int) $yandex,
            'bing'         => (int) $bing,
        ]);
    }

    /**
     * Gets the status of the specified link.
     * If the same link was sent several times, returns the status of the latest record.
     *
     * @param int $project_id Project ID
     * @param string $link The link to check
     * @return array|null Link status
     */
    public function checkLink($project_id, $link)
    {
        return $this->make_request('link/status', [
            'project_id' => $project_id,
            'link'       => $link,
        ]);
    }

    /**
     * Gets the status of a link by its ID.
     *
     * @param int $link_id Link ID
     * @return array|null Link status
     */
    public function checkLinkById($link_id)
    {
        return $this->make_request('link/status/' . $link_id);
    }

    /**
     * Resends non-indexed links of a project for indexing.
     * If $links is not passed, all non-indexed project links
     * sent fewer than $max_attempts times are used.
     *
     * @param int $project_id Project ID.
     * @param int $max_attempts Maximum number of submissions (1..10).
     * @param array|null $links Specific list of links to resend.
     * @return array|null
     */
    public function resent_notindexed_links($project_id, $max_attempts, $links = null)
    {
        return $this->make_request('link/resent_notindexed_links/' . $project_id, [
            'max_attempts' => $max_attempts,
            'links'        => $links,
        ]);
    }

    /**
     * Deletes a link or an array of links from a project.
     *
     * @param int $project_id Project ID.
     * @param string|array $link The link to delete (or an array).
     * @return array|null
     */
    public function deleteUrls($project_id, $link)
    {
        return $this->make_request('link/delete/' . $project_id, [
            'link' => $link,
        ]);
    }

    /*
    |--------------------------------------------------------------------------
    | Link source methods
    |--------------------------------------------------------------------------
    */

    /**
     * Adds a sitemap to a project.
     *
     * @param int $project_id Project ID.
     * @param string $sitemap_url Sitemap URL.
     * @param bool $google Whether to send to Google.
     * @param bool $yandex Whether to send to Yandex.
     * @param bool $bing Whether to send to Bing.
     * @param bool $google_access_granted Google account access mark.
     * @param bool $watch Whether to watch the sitemap for changes.
     * @param bool $is_external Whether to mark links from the sitemap as external.
     * @return array|null Decoded API response.
     */
    public function add_sitemap($project_id, $sitemap_url, $google, $yandex, $bing, $google_access_granted = false, $watch = false, $is_external = false)
    {
        return $this->make_request('sitemap/add', [
            'project_id'            => $project_id,
            'sitemap'               => $sitemap_url,
            'google'                => (int) $google,
            'yandex'                => (int) $yandex,
            'bing'                  => (int) $bing,
            'google_access_granted' => (int) $google_access_granted,
            'watch'                 => (int) $watch,
            'is_external'           => (int) $is_external,
        ]);
    }

    /**
     * Enables or disables watching a sitemap for changes.
     *
     * @param int $project_id Project ID.
     * @param int $sitemap_id Sitemap ID.
     * @param bool $watch New watching status (true - enable, false - disable).
     * @return array|null Decoded API response.
     */
    public function update_sitemap_watch($project_id, $sitemap_id, $watch)
    {
        return $this->make_request('sitemap/update_watch', [
            'project_id' => $project_id,
            'sitemap_id' => $sitemap_id,
            'watch'      => (int) $watch,
        ]);
    }

    /**
     * Deletes a link source (a sitemap or a text file) from a project.
     *
     * @param int $project_id Project ID.
     * @param int $sitemap_id Link source ID.
     * @return array|null Decoded API response.
     */
    public function sitemap_delete($project_id, $sitemap_id)
    {
        return $this->make_request('sitemap/delete', [
            'project_id' => $project_id,
            'sitemap_id' => $sitemap_id,
        ]);
    }

    /**
     * Gets the link sources of a project.
     * On success, returns an array of sources without the success field.
     *
     * @param int $project_id Project ID.
     * @return array|null Decoded API response.
     */
    public function link_sources($project_id)
    {
        return $this->make_request('link_sources', [
            'project_id' => $project_id,
        ]);
    }

    /**
     * Sends a request to the API.
     *
     * @param string $endpoint API endpoint to call.
     * @param array|true $post_data POST data. If true, an empty POST is sent.
     *                              An empty array means a GET request.
     * @return array|null Decoded JSON response or null on error.
     */
    private function make_request($endpoint, $post_data = [])
    {
        $ch = curl_init($this->url . $endpoint);

        if ($post_data === true) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, '');
        } elseif ($post_data) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
        }

        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $this->access_token,
            // Without this header, authorization, 404 and 429 errors are not returned as JSON
            'Accept: application/json',
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);

        // Sets the user agent; specify it if you get 403 errors
        // curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36');

        $result = curl_exec($ch);
        curl_close($ch);

        if ($result === false) {
            return null;
        }

        return json_decode($result, true);
    }
}

Chọn ngôn ngữ phù hợp: