PortModels
Log in

Complaints

Let an AI agent report a problem with supporting evidence and check its status and resolution.

AI agents and applications can submit a problem report to PortModels and check its status later. Write the title, explanation, expected behavior, and reproduction steps in English. Supporting log excerpts can retain their original language. Describe what you observed and distinguish it from your suspected cause. Never invent evidence or claim a fix was verified without a supporting check.

Submitting a complaint does not automatically run a fix or guarantee a response time. Complaints are separate from automatic error reports.

Authentication#

Use an API key with submit_complaints and view_own_complaints permissions, or a Connect token with account.manage consent and the same current account permissions. No additional complaint scopes are required with account.manage. Existing narrow complaints.write and complaints.read grants also work. Read and write access are separate. Connect reports and reads are isolated to the authenticated user and app. The server derives their identity from the token; do not submit user_id or app_id.

Apps using narrow grants must obtain consent for the corresponding read or write scope. An existing account session may need to refresh or sign in again to pick up new account permissions.

Submit a complaint#

bash
curl https://api.portmodels.com/complaints \
  -H "Authorization: Bearer $PORTMODELS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "client_complaint_id": "chat-failure-feb8e6ae",
    "language": "en",
    "title": "Chat returned an empty HTTP 400 before streaming",
    "explanation": "I sent a chat request. It returned HTTP 400 before any reply started. The response body was empty and no PortModels request ID was returned. I cannot determine which input was rejected.",
    "expected_behavior": "The API should return a reply or an explanation of the rejected request.",
    "reproduction_steps": ["Send a chat request", "Observe HTTP 400 before the first stream event"],
    "occurred_at": "2026-10-08T19:15:24Z",
    "reported_severity": "normal",
    "context": {
      "operation": "chat.completions",
      "model": "deepseek-v4.1-flash",
      "app_version": "1.2.3",
      "environment": "production",
      "agent_name": "Example agent",
      "agent_run_id": "run-123"
    },
    "evidence": [{
      "type": "request_reference",
      "client_diagnostic_id": "feb8e6ae",
      "http_status": 400,
      "occurred_at": "2026-10-08T19:15:24Z"
    }]
  }'

Use the actual failure time, version, model, and diagnostic identifiers rather than copying the example values. Include request_id whenever PortModels returned one. Timestamps must be ISO 8601 with a timezone.

For model API failures, copy the response's error.log_id into an evidence item's request_id. Keep your app's own diagnostic ID separately in client_diagnostic_id. This helps correlate the reported failure without including the original prompt, image, credentials or full request body.

The response is HTTP 201 and includes complaint.id, complaint.status, and complaint.revision. Save the ID for polling and additional evidence. The initial status is submitted when evidence is supplied.

Reuse the same client_complaint_id when retrying the same submission. An identical retry returns HTTP 200, the same complaint ID, and replayed: true. Reusing the ID for different content returns 409 idempotency_conflict. Additional evidence belongs on the existing complaint, not in a modified submission using the same ID.

Required fields and limits#

FieldRequirement
client_complaint_idRequired stable identifier; at most 128 bytes
titleRequired English summary; at most 200 bytes
explanationRequired long English text describing the action, failure, and impact; at most 20,000 bytes
expected_behaviorOptional English text; at most 4,000 bytes
reproduction_stepsOptional array of at most 20 English steps, each at most 2,000 bytes
languageen; defaults to en
reported_severitylow, normal, high, or critical; defaults to normal
evidenceAt most 20 typed items per request
evidence_unavailable_reasonRequired when evidence is empty; at most 4,000 bytes
contextOptional app_version, operation, model, environment, agent_name, agent_version, agent_run_id; each at most 256 bytes

Requests must fit within 256,000 bytes. Severity is the submitter's assessment, not a promise of priority. Write English prose even when language is omitted; the field is a declaration, not automatic translation or language detection.

If evidence cannot be collected, explain the reason and submit an empty evidence array. The complaint starts in needs_evidence.

Evidence formats#

Each item has one of these types:

TypeSupporting data
request_referenceAt least one request_id or client_diagnostic_id; optionally http_status, occurred_at, and text
log_excerptRequired text containing a small, relevant, redacted excerpt
reproductionRequired text describing a reproduction or verification result

Text is limited to 8,000 bytes per item. Identifiers are limited to 128 bytes. Only these fields are accepted: type, text, request_id, client_diagnostic_id, http_status, and occurred_at.

Remove credentials, cookies, personal data, and unrelated page content before submitting. Do not submit full HTTP captures or authorization headers. Recognizable secrets are additionally redacted before storage, and URLs lose credentials, query strings, and fragments. This does not replace careful redaction by the submitting agent. File attachments are not supported.

Add evidence#

bash
curl https://api.portmodels.com/complaints/COMPLAINT_ID/evidence \
  -H "Authorization: Bearer $PORTMODELS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "client_evidence_id": "follow-up-1",
    "evidence": [{"type": "log_excerpt", "text": "HTTP 400; no response body; no PortModels request ID."}]
  }'

Evidence is appended; the original report remains unchanged. Use a stable client_evidence_id for retry deduplication. Identical retries return 200; different evidence under the same ID returns 409. Adding evidence to a needs_evidence report returns it to submitted for review. A complaint can hold at most 100 evidence items and 200,000 bytes of evidence in total.

Check status#

bash
curl https://api.portmodels.com/complaints/COMPLAINT_ID \
  -H "Authorization: Bearer $PORTMODELS_API_KEY"

The response contains the original submission, accumulated evidence, status, revision, a status activity history, and any shared resolution. It does not expose internal review notes or other accounts' complaints.

Statuses are submitted, needs_evidence, validated, in_progress, resolved, duplicate, and not_reproducible. A resolved report includes a resolution explanation and verification evidence. Check resolution.deployment_status separately: pending, deployed, or not_required. Resolved does not by itself mean a fix has been deployed.

Use GET /complaints?limit=25&status=submitted to list your own reports. The response contains complaints and next_cursor; pass a non-null cursor as cursor on the next request. limit is 1–100. Optional since filters by server receipt time. Poll conservatively; do not repeatedly submit the same failure or poll in a tight loop.

Errors and quotas#

Errors have a JSON error object with code and message.

HTTP statusMeaning
400Invalid fields or evidence; correct the request before retrying
401Missing, expired, revoked, or inactive-account token
403Required account permission or Connect scope is missing
404Complaint absent or inaccessible to this account/app
409An identifier was reused with different content
413Request or accumulated evidence limit exceeded
429Reporting quota reached; honor Retry-After

Each account can create 20 complaints and append evidence 60 times per hour, across its apps. Identical retries do not consume another slot. Complaints are kept as a review history rather than using the seven-day automatic diagnostic retention period.