Y2 Elite workspaces are rolling out for teams
Y2Y2Docs

Call the API with curl

Read, update, paginate, and export Y2 resources from a shell

Use these recipes for direct HTTP requests. For interactive agent work, set up the Y2 command line.

Before you begin

You need curl and, for JSON processing, jq:

curl --version
jq --version
export Y2_API_KEY="y2_..."

Create a key with the scopes required by each operation. These recipes use Bash, curl, and jq.

Shell history, debug tracing, process listings, and CI output can expose secrets. Keep the key in the environment, quote variable expansions, and do not use set -x in credentialed scripts.

Create a reusable GET helper

y2.sh
#!/usr/bin/env bash
set -euo pipefail

: "${Y2_API_KEY:?Set Y2_API_KEY before running this script}"

y2_get() {
  local path="$1"
  curl --fail-with-body --silent --show-error \
    "https://api.y2.dev${path}" \
    --header "Authorization: Bearer $Y2_API_KEY" \
    --header "Accept: application/json"
}

y2_get "/api/v1/news/feeds" | jq '.meta'

Save the helper as y2.sh. Run source ./y2.sh before using y2_get below. It accepts paths such as /api/v1/news and /api/v2/incidents.

Discover News topics

List all current topic IDs and names:

y2_get "/api/v1/news/feeds" |
  jq -r '.data[] | [.id, .name, .groupLabel] | @tsv'

Then request a bounded page:

y2_get "/api/v1/news?topics=markets,macro&limit=10" |
  jq -r '.data[] | [
    .publishedAt,
    (.sentiment.label // "unknown"),
    .title,
    (.links.canonical // "")
  ] | @tsv'

News rows use title, summary, and nested sentiment fields. Get available topics from the feed directory.

Inspect profile and subscription IDs

GET /profiles returns paired resources. Print both IDs before scripting updates:

y2_get "/api/v1/profiles" |
  jq -r '.data[] | [
    .profile.id,
    .subscription.id,
    .profile.name,
    .subscription.delivery.method
  ] | @tsv'

Use prf_... for profile configuration and report filtering. Use sub_... when changing delivery.

Create a profile idempotently

profile_id=$(
  curl --fail-with-body --silent --show-error \
    "https://api.y2.dev/api/v1/profiles" \
    --request POST \
    --header "Authorization: Bearer $Y2_API_KEY" \
    --header "Content-Type: application/json" \
    --header "Idempotency-Key: supplier-risk-2026-07-21" \
    --data '{
      "name": "Critical Supplier Risk",
      "topic": "Monitor disruption affecting strategic semiconductor suppliers.",
      "frequency": "daily",
      "scheduleTimeOfDay": "08:00",
      "isCommunity": false
    }' |
    jq -r '.data.id'
)

printf '%s\n' "$profile_id"

The response ID is the profile's canonical prf_... ID. Reuse the same idempotency key only with the same request body.

Paginate every report for a profile

Y2 returns an opaque continuation link. Follow it unchanged:

export-report-index.sh
#!/usr/bin/env bash
set -euo pipefail

: "${Y2_API_KEY:?Set Y2_API_KEY}"
: "${PROFILE_ID:?Set PROFILE_ID to a prf_ ID}"

next="/api/v1/reports?profileId=$PROFILE_ID&limit=20"

while [ -n "$next" ]; do
  page=$(
    curl --fail-with-body --silent --show-error \
      "https://api.y2.dev${next}" \
      --header "Authorization: Bearer $Y2_API_KEY" \
      --header "Accept: application/json"
  )

  jq -r '.data[] | [.id, .publishedAt, (.summary // "")] | @tsv' <<<"$page"
  next=$(jq -r '.links.next // empty' <<<"$page")
done

Do not decode or edit the cursor. It is bound to the original filters and ordering.

Export one report as Markdown

: "${REPORT_ID:?Set REPORT_ID to an rpt_ ID}"

curl --fail-with-body --silent --show-error \
  "https://api.y2.dev/api/v1/reports/$REPORT_ID" \
  --header "Authorization: Bearer $Y2_API_KEY" \
  --header "Accept: text/markdown" \
  --output "$REPORT_ID.md"

The file contains the report body directly. It is not a JSON envelope.

Process NDJSON one row at a time

News and several list endpoints can return newline-delimited JSON:

curl --fail-with-body --silent --show-error --get \
  "https://api.y2.dev/api/v1/news" \
  --header "Authorization: Bearer $Y2_API_KEY" \
  --header "Accept: application/x-ndjson" \
  --data-urlencode "topics=cybersecurity" \
  --data-urlencode "limit=50" |
while IFS= read -r row; do
  jq -r '[.publishedAt, .title] | @tsv' <<<"$row"
done

For multi-page ingestion, also capture X-Y2-Next-Cursor from the response headers and send it as the next request's cursor value.

Capture status, headers, and errors

Use temporary files when a script needs to branch on HTTP status while retaining Problem Details:

body_file=$(mktemp)
headers_file=$(mktemp)
trap 'rm -f "$body_file" "$headers_file"' EXIT

status=$(
  curl --silent --show-error \
    --output "$body_file" \
    --dump-header "$headers_file" \
    --write-out '%{http_code}' \
    "https://api.y2.dev/api/v1/reports/not-a-report-id" \
    --header "Authorization: Bearer $Y2_API_KEY" \
    --header "Accept: application/json"
)

if [ "$status" -ge 400 ]; then
  jq '{status, code, detail, requestId}' "$body_file" >&2
  grep -i '^Retry-After:' "$headers_file" >&2 || true
  exit 1
fi

Log requestId, not credentials. Retry safe reads or idempotent writes only, and follow Retry-After on 429.

Call Intel v2

Intel paths use /api/v2 directly:

y2_get "/api/v2/incidents?limit=20" |
  jq -r '.data[] | [.id, .severity, .title] | @tsv'

Do not place /api/v1 before an Intel v2 path.

Know when curl is not enough

TaskAdditional requirement
x402 paymentsUse a wallet client to create an EIP-3009 signature. Send it in PAYMENT-SIGNATURE
Agent Y2 streamsUse a stream parser in applications. Keep X-Thread-Id
Webhook deliveryProvide a public HTTPS receiver. Preserve raw request bytes for signature verification