mencoro / mencoro-api-sdk
Official PHP client for the Mencoro API: projects, tracked queries, captures and AI-search visibility analytics.
Requires
- php: ^8.1
- ext-curl: *
- ext-json: *
- ext-mbstring: *
- guzzlehttp/guzzle: ^7.3
- guzzlehttp/psr7: ^1.7 || ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.5
- phpunit/phpunit: ^8.0 || ^9.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-23 00:39:01 UTC
README
Official clients for the Mencoro API — 86 operations over your projects, tracked queries, captures and the analytics computed from them.
Every client here is generated from the OpenAPI contract the API itself publishes, so they say what the running service does rather than what a handwritten wrapper remembers about it. One contract, one generator run, one version: a change to the API reaches all seven languages together.
| Language | Package | Version | Directory |
|---|---|---|---|
| Python 3.9+ | mencoro |
python/ |
|
| TypeScript (Node 18+, browsers) | @mencoro/api |
typescript/ |
|
| PHP 8.1+ | mencoro/mencoro-api-sdk |
php/ |
|
| Go 1.21+ | github.com/mencoro/mencoro-api-sdk/go |
go/ |
|
| Java 11+ | com.mencoro:mencoro-api |
java/ |
|
| .NET 8 | Mencoro.Api |
csharp/ |
|
| Ruby 3.0+ | mencoro |
ruby/ |
Each directory carries the generated reference for its language: one page per resource and one per
model under its own docs/.
Get a key
Create one from your Mencoro account, under API keys in the user menu. The plaintext key is shown once, at creation, and never again — store it where you store any other secret. If you lose it, revoke it and issue another; revocation takes effect on the very next request.
A key carries capabilities and a set of organizations, and it only ever restricts. It never grants more than its owner's live membership and role already allow, and every request re-checks those: if your role is narrowed or your membership withdrawn, the key loses that access immediately, without being revoked and without being reissued.
| Capability | What it allows |
|---|---|
read |
Every listing and every analytics operation. The floor — every key has it. |
write |
Projects, tracked queries, clusters, competitors and discovery jobs. |
organization:manage |
Members, invitations and the organization itself. Requested explicitly at creation. |
First call
getMe returns the user the key belongs to, plus the key's capabilities and scope. It is the
quickest way to confirm a key works and to see which organizations it reaches.
Python
import os from mencoro import ApiClient, Configuration from mencoro.api import AccountApi, ProjectsApi config = Configuration( host="https://api.mencoro.com", access_token=os.environ["MENCORO_API_KEY"], ) with ApiClient(config) as client: me = AccountApi(client).get_me() print(me.email, me.capabilities, me.scope_mode) for organization_id in me.organization_ids: page = ProjectsApi(client).list_projects(organization_id=organization_id, limit=100) print(organization_id, f"{len(page.items)} of {page.total} projects")
TypeScript
import { Configuration, AccountApi, ProjectsApi } from "@mencoro/api" const config = new Configuration({ basePath: "https://api.mencoro.com", accessToken: process.env.MENCORO_API_KEY, }) const me = await new AccountApi(config).getMe() console.log(me.email, me.capabilities, me.scopeMode) for (const organizationId of me.organizationIds ?? []) { const page = await new ProjectsApi(config).listProjects({ organizationId, limit: 100 }) console.log(organizationId, `${page.items?.length} of ${page.total} projects`) }
Go
import ( "context" "os" mencoro "github.com/mencoro/mencoro-api-sdk/go" ) config := mencoro.NewConfiguration() config.Servers = mencoro.ServerConfigurations{{URL: "https://api.mencoro.com"}} client := mencoro.NewAPIClient(config) ctx := context.WithValue(context.Background(), mencoro.ContextAccessToken, os.Getenv("MENCORO_API_KEY")) me, _, err := client.AccountAPI.GetMe(ctx).Execute()
PHP
use Mencoro\Api\Api\AccountApi; use Mencoro\Api\Configuration; $config = Configuration::getDefaultConfiguration() ->setHost('https://api.mencoro.com') ->setAccessToken(getenv('MENCORO_API_KEY')); $me = (new AccountApi(new GuzzleHttp\Client(), $config))->getMe();
Ruby
require "mencoro" Mencoro.configure do |config| config.host = "api.mencoro.com" config.access_token = ENV.fetch("MENCORO_API_KEY") end me = Mencoro::AccountApi.new.get_me
Java
import com.mencoro.api.ApiClient; import com.mencoro.api.api.AccountApi; ApiClient client = new ApiClient(); client.setBasePath("https://api.mencoro.com"); client.setBearerToken(System.getenv("MENCORO_API_KEY")); var me = new AccountApi(client).getMe();
C#
using Mencoro.Api.Api; using Mencoro.Api.Client; var config = new Configuration { BasePath = "https://api.mencoro.com", AccessToken = Environment.GetEnvironmentVariable("MENCORO_API_KEY"), }; var me = new AccountApi(config).GetMe();
Conventions
These hold in every language. The per-language docs cover the signatures; this is what the contract cannot state about itself.
Errors
Every refusal answers the same envelope, and every one carries a requestId. Quote it to support
and it identifies your exact request.
{
"code": "validation_error",
"message": "The request failed validation.",
"requestId": "01a0c58a-3370-7170-acdb-a0a7b760bc83",
"details": {
"limit": [
{ "code": "out_of_range", "message": "\"limit\" must be between 1 and 100." }
]
}
}
details is keyed by field, and each entry carries one of seven codes: missing_field,
invalid_type, invalid_value, invalid_uuid, invalid_email, out_of_range, unknown_field.
An unrecognised parameter is reported rather than ignored, so a typo in a filter never silently
hands you the unfiltered set.
Rate limits
600 requests per user and 300 per key, every 60 seconds. The per-user ceiling is the real one: issuing more keys divides the same budget rather than adding to it.
Over budget answers 429 with a Retry-After header in seconds. Honour it:
import time from mencoro.exceptions import ApiException def with_backoff(call, attempts=3): for attempt in range(attempts): try: return call() except ApiException as error: if error.status != 429 or attempt == attempts - 1: raise time.sleep(int(error.headers.get("Retry-After", "1")))
Writes are idempotent
Every write requires an Idempotency-Key — 8 to 255 printable ASCII characters with no spaces,
and a UUID is the obvious choice. Reuse it when you retry and the stored result is replayed rather
than the operation running again, so a lost response never becomes a duplicate project or a second
batch of tracked queries.
import uuid from mencoro.api import TrackedQueriesApi from mencoro.models import BatchCreateTrackedQueriesRequestData TrackedQueriesApi(client).batch_create_tracked_queries( organization_id=organization_id, project_id=project_id, idempotency_key=str(uuid.uuid4()), batch_create_tracked_queries_request_data=BatchCreateTrackedQueriesRequestData( query_texts=["best crm for startups"], engines=["chatgpt", "perplexity"], countries=["ES"], check_frequency="daily", n_passes=3, query_cluster_ids=[], ), )
Generate the key outside the retry loop. A fresh key per attempt is the same as having no idempotency at all.
A key is scoped to the API key that used it and bound to the request it first answered — method,
path and body. Reusing it for a different request is refused with idempotency_key_reused rather
than answering the wrong operation.
Changes to the organization are confirmed
Archiving an organization, changing a member's role, inviting somebody — anything whose effects reach beyond your own data — takes two calls:
from mencoro.api import MembersApi, OrganizationOperationsApi from mencoro.models import PreviewOrganizationOperationRequest preview = OrganizationOperationsApi(client).preview_organization_operation( preview_organization_operation_request=PreviewOrganizationOperationRequest( action="change_member_role", organization_id=organization_id, resource_id=member_id, payload={"role": "manager"}, ), ) print(preview.changes, preview.side_effects, preview.warnings) MembersApi(client).change_member_role( organization_id=organization_id, member_id=member_id, x_mencoro_confirmation=preview.confirmation.token, idempotency_key=str(uuid.uuid4()), change_member_role_request=..., )
The token is single use, valid for five minutes, and bound to the effects the preview declared. If
the organization changed in between, the call is refused with 409 rather than doing more than was
agreed. Without the header it answers 428.
Paging
Listings take limit (default 20, maximum 100) and offset. A limit above the maximum is
refused, never quietly clamped, so the page you asked for is the page you get. total counts
everything the filter matches, not the size of the page.
Several listings also answer text/csv when asked for it with an Accept header. That is the same
page of the same rows, encoded differently: the 100-row maximum applies unchanged, because it is a
page and not a bulk export.
Dates
YYYY-MM-DD, covering whole days in UTC.
Also available over MCP
To read the same data inside an AI client rather than from your own code, the Mencoro MCP server exposes it to Claude, ChatGPT, Cursor and anything else that speaks MCP. Same data, no code to write.
Regenerating
./generate.sh # every SDK, from the committed openapi.json ./generate.sh python go # only these ./generate.sh --sync # fetch the live contract first, then regenerate everything
Requires Docker; nothing else. Everything inside each language directory is generated and must not
be edited by hand — the next run discards it. Adding a language is one entry in
sdks.json.
The fetch is opt-in because a regeneration that silently pulls a new contract turns a mechanical rerun into a content change nobody reviewed, and the diff runs to six figures of lines across seven languages — the one place a surprise must not hide.
Two corrections are applied by the generator script rather than by hand, because a hand edit would be discarded on the next run:
- Go has no package registry, so the import path is the repository URL plus the directory. The
generator derives the module path from the Go package name, and
gois a keyword, so it cannot produce.../mencoro-api-sdk/goon its own. - PHP's generated models all declare
getModelName()to report their schema name, which collides with the contract's ownmodelNameproperty onAiResponseResource— two methods of one name is a fatal parse error. The property is renamed toengineModelNamein the PHP client only; the wire field is untouched.
Contract
openapi.json— the contract every client here is generated from- Live contract and browsable reference
- API documentation
llms-full.txt— the whole reference as plain text, for pasting into an AI assistant
License
MIT. See LICENSE. Use of the Mencoro API itself is governed by Mencoro's terms.