Cat recognition API guide

From the first enrollment photo to an identity search.

Every endpoint is a plain HTTP request: create cats in a project, enroll images, search identities and read task status.

Public documentation · No login required Illustrated API integration guide ↗Follow 12 annotated screenshots to create a cat profile and make your first recognition API request.Zhongdamaopu integration ↗Connect your campus-cat mini program: create a project, configure a key, sync approved photos, and verify recognition.

Before your first request

  1. Sign in to the platform console, create a project and copy its full UUID.
  2. Create a project API key, set its scopes and save the secret, which is shown only once.
  3. Create a cat identity, keep the returned id and upload enrollment images.
  4. Check the task list. Once enrollment completes, upload a query image to get ranked matches.
Open the platform console ↗
In these examples, {{base_url}}is your HTTPS API service address, and {{api_key}} is your project API key. The API address comes from your platform deployment. This website serves documentation, not API traffic.

cURL examples use Bash line continuations. Windows users can import them into Postman with Import → Raw text, set base_url and api_key, and reselect upload files. Run Python examples on your server with requests installed and the environment variables configured.

Project-scoped credentials

Business endpoints accept Authorization: Bearer mk_live_… or the project owner's login token. Each key is bound to a project and can access project data allowed by its scopes.

Scope Allowed operations
cats:readRead identities, image lists and image content
cats:writeManage identities, enroll images, set primary photos and select enrollment candidates
recognition:writeRun cat identity searches
tasks:readRead tasks, results, summaries and preview images

Key status, revocation, expiry and IP allowlists are all checked during authentication. Creating a key without scopes grants all four. The retry endpoint accepts either cats:write or recognition:write.

Project creation and settings, key management, wallets, billing and usage queries require a login token rather than an API key. Store secrets in server-side environment variables.

Enroll asynchronously, query for results

Enrollment returns HTTP 202 with task_id. vectors_added: 0 is expected at this stage. Images become searchable after background feature extraction. Recognition requests are prioritized and wait for inference before returning results.

queued → running → completed
                 → waiting_user → selection → queued
                 → failed → retry → queued

Poll GET /tasks?limit=10&offset=0 every few seconds and find task_id by id. There is no standalone GET /tasks/ {task_id} detail endpoint. Tasks are ordered by creation time descending. Paginate with offset and deduplicate by task ID.

Multiple cats in an enrollment image may result in waiting_user. Inspect the candidate images and submit candidate_index through selection, or use cancel to abandon the pending enrollment. Only failed tasks can be retried.

A request timeout does not imply task failure. Check the task list before uploading again to avoid duplicate work and charges. These endpoints do not provide general Idempotency-Key deduplication.

Limits, billing and error handling

Default per-key limits per second are 2 recognition requests, 10 enrollment/write requests and 20 reads. Deployment settings may override them. On 429, respect Retry-After. Normal successful JSON responses expose X-RateLimit-Limit and X-RateLimit-Remaining.

The configurable default image limit is 15 MiB. JPEG, PNG and WebP are supported. Enrollment and recognition charge for cat embedding extraction; consult the console for current prices and grants. Error detail can be a string, structured object or validation array. Keep the HTTP status and X-Request-Id.

HTTP Error code / detail Description
401invalid_api_keyInvalid format, secret, or unknown key
401expired_api_keyKey has expired
403api_key_disabled / api_key_revokedKey is disabled or revoked
403project_mismatchKey belongs to another project
403scope_deniedRequired scope is missing
403ip_not_allowedSource IP is not allowed
404Not foundProject, identity, image, or task not found
409ConflictTask state does not allow this operation
413Image is too largeUpload exceeds the configured size limit
415Unsupported media typeOnly JPEG, PNG, and WebP are supported
422Validation errorCheck required fields, types, and ranges
429rate_limit_exceededReduce concurrency and respect Retry-After
503Recognition task failedInference failed; check task state before retrying

Create an identity

POST/v1/projects/{project_id}/cats

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

name is 1–100 characters and required at creation; metadata is a JSON object; active defaults to true. Send only the fields you want to update.

cURL / Postman

curl --request POST '{{base_url}}/v1/projects/{project_id}/cats' \
  --header 'Authorization: Bearer {{api_key}}' \
  --header 'Content-Type: application/json' \
  --data '{"name":"Mimi","metadata":{},"active":true}'
View Python example
import os
import json
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.post(
    base_url + "/v1/projects/{project_id}/cats",
    headers={"Authorization": f"Bearer {api_key}"},
    json=json.loads("{\"name\":\"Mimi\",\"metadata\":{},\"active\":true}"),
    timeout=120,
)
response.raise_for_status()
print(response.content)

List identities

GET/v1/projects/{project_id}/cats

Required scope: cats:read. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

cURL / Postman

curl --request GET '{{base_url}}/v1/projects/{project_id}/cats' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.get(
    base_url + "/v1/projects/{project_id}/cats",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Update an identity

PATCH/v1/projects/{project_id}/cats/{cat_id}

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

name is 1–100 characters and required at creation; metadata is a JSON object; active defaults to true. Send only the fields you want to update.

cURL / Postman

curl --request PATCH '{{base_url}}/v1/projects/{project_id}/cats/{cat_id}' \
  --header 'Authorization: Bearer {{api_key}}' \
  --header 'Content-Type: application/json' \
  --data '{"name":"Mimi","active":true}'
View Python example
import os
import json
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.patch(
    base_url + "/v1/projects/{project_id}/cats/{cat_id}",
    headers={"Authorization": f"Bearer {api_key}"},
    json=json.loads("{\"name\":\"Mimi\",\"active\":true}"),
    timeout=120,
)
response.raise_for_status()
print(response.content)

Delete an identity

DELETE/v1/projects/{project_id}/cats/{cat_id}

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

Deleting an identity removes its images and vectors; deleting an image removes its corresponding vectors. Identity deletion returns 204 with no body.

cURL / Postman

curl --request DELETE '{{base_url}}/v1/projects/{project_id}/cats/{cat_id}' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.delete(
    base_url + "/v1/projects/{project_id}/cats/{cat_id}",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Enroll an image

POST/v1/projects/{project_id}/cats/{cat_id}/images

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

Use multipart/form-data with the required file field image . Upload one JPEG, PNG or WebP per request. Let the client generate Content-Type and boundary.

Returns 202 queued with task_id. Track enrollment in the task list.

cURL / Postman

curl --request POST '{{base_url}}/v1/projects/{project_id}/cats/{cat_id}/images' \
  --header 'Authorization: Bearer {{api_key}}' \
  --form '[email protected];type=image/jpeg'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
with open("cat.jpg", "rb") as image:
    response = requests.post(
        base_url + "/v1/projects/{project_id}/cats/{cat_id}/images",
        headers={"Authorization": f"Bearer {api_key}"},
        files={"image": ("cat.jpg", image, "image/jpeg")},
        timeout=120,
    )
response.raise_for_status()
print(response.content)

List enrolled images

GET/v1/projects/{project_id}/cats/{cat_id}/images

Required scope: cats:read. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

cURL / Postman

curl --request GET '{{base_url}}/v1/projects/{project_id}/cats/{cat_id}/images' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.get(
    base_url + "/v1/projects/{project_id}/cats/{cat_id}/images",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Download an image

GET/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}/content

Required scope: cats:read. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

The success response is binary image content, not JSON.

cURL / Postman

curl --request GET '{{base_url}}/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}/content' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.get(
    base_url + "/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}/content",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Set primary image

PATCH/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}/primary

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

cURL / Postman

curl --request PATCH '{{base_url}}/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}/primary' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.patch(
    base_url + "/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}/primary",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Delete an image

DELETE/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

Deleting an identity removes its images and vectors; deleting an image removes its corresponding vectors. Identity deletion returns 204 with no body.

cURL / Postman

curl --request DELETE '{{base_url}}/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.delete(
    base_url + "/v1/projects/{project_id}/cats/{cat_id}/images/{image_id}",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

List tasks and results

GET/v1/projects/{project_id}/tasks

Required scope: tasks:read. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

limit ranges from 1 to 200, default 100; offset is at least 0, default 0. The returned array contains task statuses and result.

cURL / Postman

curl --request GET '{{base_url}}/v1/projects/{project_id}/tasks?limit=10&offset=0' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.get(
    base_url + "/v1/projects/{project_id}/tasks?limit=10&offset=0",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Task summary

GET/v1/projects/{project_id}/tasks/summary

Required scope: tasks:read. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

cURL / Postman

curl --request GET '{{base_url}}/v1/projects/{project_id}/tasks/summary' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.get(
    base_url + "/v1/projects/{project_id}/tasks/summary",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Download query image

GET/v1/projects/{project_id}/tasks/{task_id}/query

Required scope: tasks:read. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

The success response is binary image content, not JSON.

cURL / Postman

curl --request GET '{{base_url}}/v1/projects/{project_id}/tasks/{task_id}/query' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.get(
    base_url + "/v1/projects/{project_id}/tasks/{task_id}/query",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Download candidate image

GET/v1/projects/{project_id}/tasks/{task_id}/candidates/{candidate_index}

Required scope: tasks:read. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

The success response is binary image content, not JSON.

cURL / Postman

curl --request GET '{{base_url}}/v1/projects/{project_id}/tasks/{task_id}/candidates/{candidate_index}' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.get(
    base_url + "/v1/projects/{project_id}/tasks/{task_id}/candidates/{candidate_index}",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Select enrollment candidate

POST/v1/projects/{project_id}/tasks/{task_id}/selection

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

Only available for enrollment tasks in waiting_user. Other states return 409. candidate_index is a nonnegative integer.

cURL / Postman

curl --request POST '{{base_url}}/v1/projects/{project_id}/tasks/{task_id}/selection' \
  --header 'Authorization: Bearer {{api_key}}' \
  --header 'Content-Type: application/json' \
  --data '{"candidate_index":0}'
View Python example
import os
import json
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.post(
    base_url + "/v1/projects/{project_id}/tasks/{task_id}/selection",
    headers={"Authorization": f"Bearer {api_key}"},
    json=json.loads("{\"candidate_index\":0}"),
    timeout=120,
)
response.raise_for_status()
print(response.content)

Abandon pending selection

POST/v1/projects/{project_id}/tasks/{task_id}/cancel

Required scope: cats:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

Only available for enrollment tasks in waiting_user. Other states return 409. candidate_index is a nonnegative integer.

cURL / Postman

curl --request POST '{{base_url}}/v1/projects/{project_id}/tasks/{task_id}/cancel' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.post(
    base_url + "/v1/projects/{project_id}/tasks/{task_id}/cancel",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Retry a failed task

POST/v1/projects/{project_id}/tasks/{task_id}/retry

Required scope: cats:write / recognition:write. Also accepts the project owner's login token. Replace all path placeholders with actual IDs.

Only failed tasks with retained source data can be retried. Returns 202 on success.

cURL / Postman

curl --request POST '{{base_url}}/v1/projects/{project_id}/tasks/{task_id}/retry' \
  --header 'Authorization: Bearer {{api_key}}'
View Python example
import os
import requests

api_key = os.environ["MEOWID_API_KEY"]
base_url = os.environ["MEOWID_API_BASE"].rstrip("/")
# Replace {project_id}, {cat_id}, etc. with actual IDs.
response = requests.post(
    base_url + "/v1/projects/{project_id}/tasks/{task_id}/retry",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=120,
)
response.raise_for_status()
print(response.content)

Addresses and keys in the examples are placeholders. Replace them with your own settings.