# Goyondo agent quickstart

Copy one of the scripts below to go from **no credential** to a successful `list_trips` call.

Canonical base URL: `https://goyondo.run`

Do **not** scrape `/connect` first. Start at `POST /api/agent-auth/device`, show the user `verification_uri_complete`, then poll `POST /api/agent-auth/token`.

The access token is a `gyd_*` API key. It is revealed **once** on the successful poll. Store it securely. Device-auth keys default to a 90-day TTL.

An empty trip list is a valid success. New accounts often return `data: []`.

## Common mistakes

- Do not open or scrape `https://goyondo.run/connect` to discover auth. Call `/api/agent-auth/device` first.
- Poll `/api/agent-auth/token`, not `/connect`.
- Send tasks as `{ "capability": "list_trips", "arguments": {} }`. Flat aliases at the top level are also accepted.
- Do not nest fields under `body`. Prefer `arguments`. The server also unwraps `params`, `parameters`, `data`, `activity`, `updates`, and `changes`, but `arguments` is the published contract.

`authorization_pending` is normal while the user is still on `/connect`. Stop on `expired_token`, `access_denied`, `invalid_grant`, or any other non-pending error and restart device auth.

## Expected `list_trips` shape

`POST /api/agent/task` wraps the REST payload in `result`:

```json
{
  "success": true,
  "result": {
    "success": true,
    "data": [],
    "count": 0,
    "has_more": false
  }
}
```

A non-empty account looks the same with trip objects in `result.data`. You do not need seeded demo trips to finish this quickstart.

---

## curl

Requires `curl` and `jq`. Ask the user to open the printed URL and tap **Grant access**.

```bash
set -euo pipefail
BASE=https://goyondo.run

DEVICE=$(curl -sS -X POST "$BASE/api/agent-auth/device" \
  -H 'Content-Type: application/json' \
  -d '{"agent_name":"Quickstart","requested_scopes":["trips:read"]}')

echo "$DEVICE" | jq '{user_code, verification_uri_complete, expires_in, interval}'
DEVICE_CODE=$(echo "$DEVICE" | jq -r .device_code)
INTERVAL=$(echo "$DEVICE" | jq -r '.interval // 5')
echo "Ask the user to open: $(echo "$DEVICE" | jq -r .verification_uri_complete)"

while true; do
  TOKEN_JSON=$(curl -sS -w '\n%{http_code}' -X POST "$BASE/api/agent-auth/token" \
    -H 'Content-Type: application/json' \
    -d "{\"device_code\":\"$DEVICE_CODE\"}")
  HTTP=$(echo "$TOKEN_JSON" | tail -n1)
  BODY=$(echo "$TOKEN_JSON" | sed '$d')
  ERR=$(echo "$BODY" | jq -r '.error // empty')
  if [ "$HTTP" = "200" ] && [ "$(echo "$BODY" | jq -r '.access_token // empty')" != "" ]; then
    TOKEN=$(echo "$BODY" | jq -r .access_token)
    echo "Store this token now. It will not be shown again."
    break
  fi
  if [ "$ERR" = "authorization_pending" ]; then
    sleep "$INTERVAL"
    continue
  fi
  echo "Token poll failed ($HTTP): $BODY" >&2
  exit 1
done

curl -sS -X POST "$BASE/api/agent/task" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"capability":"list_trips","arguments":{}}' | jq .
```

---

## Python (`requests`)

```python
# pip install requests
import json
import time
import requests

BASE = "https://goyondo.run"

device = requests.post(
    f"{BASE}/api/agent-auth/device",
    json={"agent_name": "Quickstart", "requested_scopes": ["trips:read"]},
    timeout=30,
).json()
print("Ask the user to open:", device["verification_uri_complete"])
interval = int(device.get("interval") or 5)

token = None
while token is None:
    res = requests.post(
        f"{BASE}/api/agent-auth/token",
        json={"device_code": device["device_code"]},
        timeout=30,
    )
    body = res.json()
    if res.status_code == 200 and body.get("access_token"):
        token = body["access_token"]
        print("Store this token now. It will not be shown again.")
        break
    err = body.get("error")
    if err == "authorization_pending":
        time.sleep(interval)
        continue
    raise SystemExit(f"Token poll failed ({res.status_code}): {body}")

task = requests.post(
    f"{BASE}/api/agent/task",
    headers={"Authorization": f"Bearer {token}"},
    json={"capability": "list_trips", "arguments": {}},
    timeout=30,
)
print(json.dumps(task.json(), indent=2))
task.raise_for_status()
```

---

## TypeScript (`fetch`)

Works on Node 18+ (global `fetch`).

```ts
const BASE = "https://goyondo.run";

type DeviceResponse = {
  device_code: string;
  verification_uri_complete: string;
  interval?: number;
};

type TokenBody = {
  access_token?: string;
  error?: string;
  error_description?: string;
};

const deviceRes = await fetch(`${BASE}/api/agent-auth/device`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    agent_name: "Quickstart",
    requested_scopes: ["trips:read"],
  }),
});
const device = (await deviceRes.json()) as DeviceResponse;
console.log("Ask the user to open:", device.verification_uri_complete);
const intervalMs = (device.interval ?? 5) * 1000;

let token: string | undefined;
while (!token) {
  const tokenRes = await fetch(`${BASE}/api/agent-auth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ device_code: device.device_code }),
  });
  const body = (await tokenRes.json()) as TokenBody;
  if (tokenRes.ok && body.access_token) {
    token = body.access_token;
    console.log("Store this token now. It will not be shown again.");
    break;
  }
  if (body.error === "authorization_pending") {
    await new Promise((r) => setTimeout(r, intervalMs));
    continue;
  }
  throw new Error(`Token poll failed (${tokenRes.status}): ${JSON.stringify(body)}`);
}

const taskRes = await fetch(`${BASE}/api/agent/task`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ capability: "list_trips", arguments: {} }),
});
const taskJson = await taskRes.json();
console.log(JSON.stringify(taskJson, null, 2));
if (!taskRes.ok) throw new Error("list_trips failed");
```

---

## Next

- Agent card: `GET https://goyondo.run/api/agent/card`
- Device-auth details: `https://goyondo.run/auth.md`
- Overview: `https://goyondo.run/llms.txt`
- Human pricing: `https://goyondo.run/pricing`
- Examples repo (not an SDK): `https://github.com/agteo/goyondo-agent-examples`
