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
- Sign in to the platform console, create a project and copy its full UUID.
- Create a project API key, set its scopes and save the secret, which is shown only once.
- Create a cat identity, keep the returned id and upload enrollment images.
- 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:read | Read identities, image lists and image content |
cats:write | Manage identities, enroll images, set primary photos and select enrollment candidates |
recognition:write | Run cat identity searches |
tasks:read | Read 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 → queuedPoll 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 |
|---|
| 401 | invalid_api_key | Invalid format, secret, or unknown key |
| 401 | expired_api_key | Key has expired |
| 403 | api_key_disabled / api_key_revoked | Key is disabled or revoked |
| 403 | project_mismatch | Key belongs to another project |
| 403 | scope_denied | Required scope is missing |
| 403 | ip_not_allowed | Source IP is not allowed |
| 404 | Not found | Project, identity, image, or task not found |
| 409 | Conflict | Task state does not allow this operation |
| 413 | Image is too large | Upload exceeds the configured size limit |
| 415 | Unsupported media type | Only JPEG, PNG, and WebP are supported |
| 422 | Validation error | Check required fields, types, and ranges |
| 429 | rate_limit_exceeded | Reduce concurrency and respect Retry-After |
| 503 | Recognition task failed | Inference failed; check task state before retrying |
Recognize a cat
POST/v1/projects/{project_id}/search
Required scope: recognition: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.
Query parameters and results
top_k is an integer from 1 to 100, default 5 (configurable); threshold is an optional similarity cutoff from −1 to 1. The model default is used when omitted.
matches contains candidates ranked by similarity; unknown=true means no reliable identity met the threshold. In candidates , score is the detection score, not identity similarity. Detecting a face does not mean identifying the cat.
{
"cat_count": 1,
"route": "face",
"face_detected": true,
"unknown": true,
"matches": [],
"candidates": [
{
"index": 0,
"score": 0.95,
"route": "face",
"face_detected": true,
"unknown": true,
"matches": []
}
],
"task_id": "<task_id>"
}Example response structure, not a live result.
curl --request POST '{{base_url}}/v1/projects/{project_id}/search?top_k=5' \
--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}/search?top_k=5",
headers={"Authorization": f"Bearer {api_key}"},
files={"image": ("cat.jpg", image, "image/jpeg")},
timeout=120,
)
response.raise_for_status()
print(response.content)
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 --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 --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 --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 --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 --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 --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 --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 --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 --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 --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 --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 --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 --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 --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 --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 --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.