CyberDeduct← Home

OpenAPI 3.1 spec: /api/portal-v1.yaml

CyberDeduct Portal API

Base URL: https://cyberdeduction.initial-energy.com

A portal key lets your own bot read your trips and file suggestions. A suggestion does not change a trip. You confirm or dismiss it in CyberDeduct (or with your session token). Until you accept, the miles, category, purpose, and signature stay as they were.

The machine-readable description is /api/portal-v1.yaml. Send it to the assistant as the tool schema. Authenticate the bot with the portal key, not your session token.

Create a key

Keys are managed by the signed-in user. The account must be active.

curl -s -X POST "$BASE/portal/keys" \
  -H "Authorization: Bearer $SESSION" \
  -H "Content-Type: application/json" \
  -d '{"name":"Trip bot","scopes":["read","suggest"]}'

The response includes key once (cdk_...). Store it in the assistant's secret field. Later calls show only the prefix.

curl -s "$BASE/portal/keys" -H "Authorization: Bearer $SESSION"
curl -s -X DELETE "$BASE/portal/keys/$KEY_ID" -H "Authorization: Bearer $SESSION"

A revoked key receives 401.

Scopes

Scope Allows
read GET /portal/v1/trips and GET /portal/v1/review-queue
suggest POST /portal/v1/trips/{id}/suggestions

Omitting scopes (or sending none that are recognized) creates a read key. Unknown scope names are dropped. A read-only key that posts a suggestion receives 403. A suggest-only key that lists trips receives 403. Give a bot both.

Rate limit

Each key may make 60 requests per 60 seconds. Reads and suggestion writes share that per-key bucket. The limiter fails open if its storage is briefly unavailable.

429 body:

{ "error": "rate_limited", "message": "Too many requests. Please wait a moment and try again." }

Wait and retry. The query-string email is ignored; the key's owner is the caller.

Read trips

from and to are YYYY-MM-DD (inclusive) on the trip's local date. limit defaults to 50 and caps at 200. Follow nextCursor until it is null.

curl -s "$BASE/portal/v1/trips?from=2026-01-01&to=2026-01-31" \
  -H "Authorization: Bearer $PORTAL_KEY"

curl -s "$BASE/portal/v1/review-queue" \
  -H "Authorization: Bearer $PORTAL_KEY"

The review queue is trips whose purpose is still unconfirmed. Each item can include matching appointments and any pending suggestions. Responses are a whitelist: no signatures, coordinates, odometers, or notes.

Trip ids look like {vin}:{start}. URL-encode the id when you put it in a path.

File a suggestion

TRIP_ID='5YJ3E1EA7KF000000:2026-04-02T15:00:00.000Z'
ENC=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$TRIP_ID")

curl -s -X POST "$BASE/portal/v1/trips/$ENC/suggestions" \
  -H "Authorization: Bearer $PORTAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"category":"business","purpose":"Client meeting","note":"Matches the 2pm site visit","source":"grok"}'

404 means that trip is not yours. The latest pending suggestion for the same trip and key replaces the older one. The trip row is not modified.

Confirm in the app

The bot cannot accept its own suggestion. In CyberDeduct, review the pending list and accept or reject. The same calls, with your session token:

curl -s "$BASE/portal/suggestions" -H "Authorization: Bearer $SESSION"

curl -s -X POST "$BASE/portal/suggestions/$SUGGESTION_ID/accept" \
  -H "Authorization: Bearer $SESSION"

curl -s -X POST "$BASE/portal/suggestions/$SUGGESTION_ID/reject" \
  -H "Authorization: Bearer $SESSION"

Accept applies the category and purpose through the same correction used when you edit a trip yourself: the previous values are kept in a dated edit record, and the record's origin is portal suggestion. The trip is then your confirmed edit. Reject only marks the suggestion rejected; the trip stays as it was.

Another user's session cannot accept your suggestion (404).

Point Grok, ChatGPT, or Claude at it

  1. Create a key with read and suggest.
  2. Import /api/portal-v1.yaml as the action / tool schema. Set the server URL to the base URL above.
  3. Set auth to HTTP Bearer and paste the portal key (cdk_...). Do not paste your session token.
  4. Tell the assistant it may read the review queue and file suggestions, and that a suggestion is not confirmed until you accept it in CyberDeduct.

ChatGPT: Custom GPT → Actions → import the schema → Authentication → Bearer.

Claude: add the schema as a tool (or an MCP/proxy in front of these routes) with the same Bearer key.

Grok: add a custom connector or tool from the schema, Bearer auth, and the same instruction. The model calls GET /portal/v1/review-queue and POST /portal/v1/trips/{id}/suggestions. It does not call the accept route.