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
#!/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:
#!/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")
doneDo 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"
doneFor 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
fiLog 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
| Task | Additional requirement |
|---|---|
| x402 payments | Use a wallet client to create an EIP-3009 signature. Send it in PAYMENT-SIGNATURE |
| Agent Y2 streams | Use a stream parser in applications. Keep X-Thread-Id |
| Webhook delivery | Provide a public HTTPS receiver. Preserve raw request bytes for signature verification |