# API에 첫 요청은 어떻게 보냅니까?

여기서 시작하십시오 · URL 하나, 키 없음, 1초 안에 돌아오는 JSON

*API 가이드 · 6분 · 입문 · 개발자 · 시작*

HTTPS를 쓸 수 있는 어떤 환경에서든 `GET https://stablescan.achivx.com/v1/networks`를 보내십시오. 키도, 가입도, `/api` 접두사도 없습니다. REST는 루트의 `/v1`에 있습니다. 응답은 `ETag`, `Cache-Control`, `X-Request-Id`가 붙은 JSON입니다. 2026-10-24에는 0.31초 만에 스캐너가 아는 아홉 개 네트워크와 함께 돌아왔으며, 그중 일곱 개가 `settled: true`였습니다. 아래의 세 가지 헬퍼 — Python, Go, TypeScript — 는 이 섹션의 다른 모든 페이지가 호출하는 것입니다.

## 요청

```
curl -s https://stablescan.achivx.com/v1/networks
```

이것이 전부입니다. 필수 헤더는 없으며, 어차피 `Accept: application/json`으로 받습니다. 2026-10-24의 응답을 처음 세 행으로 줄였습니다.

```
{
  "items": [
    {"chain_id": -1, "name": "Solana",   "caip2": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "settled": true, "slug": "solana"},
    {"chain_id": 1,  "name": "Ethereum", "caip2": "eip155:1",  "settled": true, "slug": "ethereum"},
    {"chain_id": 10, "name": "Optimism", "caip2": "eip155:10", "settled": true, "slug": "optimism"}
  ]
}
```

모두 아홉 행입니다. 솔라나(Solana), 이더리움(Ethereum), 옵티미즘(Optimism), BNB 체인(BNB Chain)(56), 베이스(Base)(8453), 아비트럼 원(Arbitrum One)(42161), 트론(Tron)(728126428)은 `settled: true`로, 여기서 인덱싱되며 질의할 수 있습니다. 폴리곤(Polygon)(137)과 아발란체(Avalanche)(43114)는 `settled: false` — *알려져 있지만 여기서 인덱싱되지 않음* — 이며, 이들을 지정한 `chain`은 거부됩니다. 이 목록을 보관해 두십시오. 아래의 체인 범위 호출은 모두 이 ID, 슬러그, CAIP-2 ID 중 하나를 `chain`으로 받습니다.

## 함께 돌아온 것

같은 응답의 헤더 중 읽어 볼 만한 것들입니다.

```
HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: public, max-age=300, stale-while-revalidate=60, stale-if-error=3600
etag: "8fc4bafb57ec938fe40dc8793281336e"
x-request-id: a4f864251df5c8cb-ARN
x-robots-tag: noindex
cf-cache-status: HIT
```

- **`cache-control`**은 응답이 얼마 동안 유효한지 알려 줍니다. 레지스트리는 300초, 판정은 60초, [피드](https://achivx.kr/academy/courses/following-the-stablecoins/following-the-stablecoins-01-settlement-tables/)는 15초, 동기화 상태는 5초입니다. [코드에서의 페이지 넘김과 캐시](https://achivx.kr/academy/api/paging-and-caching-in-code/)가 이를 사용합니다.
- **`etag`**는 재검증에 씁니다. `If-None-Match`로 돌려보내면 일치하는 응답은 본문 없는 `304`입니다(2026-10-24 `/v1/networks`에서 확인).
- **`x-request-id`**는 응답의 이름입니다. 직접 `X-Request-Id`를 보내면 그대로 돌려받습니다(`/v1/sanctions`에서 확인). 엣지 캐시된 조회에서는 대신 위와 같은 `<16 hex>-<colo>` 형식의 Cloudflare ray입니다. 응답을 보고할 때 함께 알려 주십시오.
- **`x-robots-tag: noindex`**는 크롤러에게 JSON을 페이지로 인덱싱하지 말라고 요청할 뿐입니다. 접근 규칙이 아니므로 라우트를 직접 호출하십시오.

## 코드로 같은 요청 보내기

언어별로 하나씩, 작은 헬퍼 세 개입니다. 각각 URL을 만들고, 설정할 가치가 있는 헤더 두 개를 설정하고, `4xx`/`5xx`를 스캐너 자체의 `error.code`와 `message`를 담은 오류로 바꾸고, JSON을 디코딩합니다. 이 섹션의 다른 모든 페이지는 이 세 함수를 기준으로 예제를 작성합니다. Python의 `scan(path, **params)`, Go의 `get(path, params, &out)`, TypeScript의 `scan<T>(path, params)`입니다. 표준 라이브러리 외에는 아무것도 쓰지 않습니다(Python 3.8+, Go 1.20+, Node 18+ 또는 `fetch`가 있는 모든 런타임).

**Python**

```
import json, urllib.parse, urllib.request

BASE = "https://stablescan.achivx.com"

def scan(path, **params):
    """GET one /v1 answer as a dict. A 4xx/5xx raises urllib.error.HTTPError; its body is the error envelope."""
    url = BASE + path
    if params:
        url += "?" + urllib.parse.urlencode(params)
    req = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": "my-integration/1.0"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)

networks = scan("/v1/networks")
for n in networks["items"]:
    if n["settled"]:
        print(n["chain_id"], n["slug"], n["caip2"])
```

**Go**

```
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"
)

const base = "https://stablescan.achivx.com"

var client = &http.Client{Timeout: 30 * time.Second}

// apiError is the one error envelope every /v1 route answers with.
type apiError struct {
	Status    int
	Code      string `json:"code"`
	Message   string `json:"message"`
	RequestID string `json:"request_id"`
}

func (e *apiError) Error() string {
	return fmt.Sprintf("%d %s: %s (request %s)", e.Status, e.Code, e.Message, e.RequestID)
}

// get decodes one /v1 answer into out; a 4xx/5xx comes back as *apiError.
func get(path string, params url.Values, out any) error {
	u := base + path
	if len(params) > 0 {
		u += "?" + params.Encode()
	}
	req, err := http.NewRequest(http.MethodGet, u, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", "my-integration/1.0")
	res, err := client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		var env struct {
			Error apiError `json:"error"`
		}
		_ = json.NewDecoder(res.Body).Decode(&env)
		env.Error.Status = res.StatusCode
		return &env.Error
	}
	return json.NewDecoder(res.Body).Decode(out)
}

func main() {
	var networks struct {
		Items []struct {
			ChainID int    `json:"chain_id"`
			Slug    string `json:"slug"`
			CAIP2   string `json:"caip2"`
			Settled bool   `json:"settled"`
		} `json:"items"`
	}
	if err := get("/v1/networks", nil, &networks); err != nil {
		panic(err)
	}
	for _, n := range networks.Items {
		if n.Settled {
			fmt.Println(n.ChainID, n.Slug, n.CAIP2)
		}
	}
}
```

**TypeScript**

```
const BASE = "https://stablescan.achivx.com";

type Params = Record<string, string | number | boolean | undefined>;

export class ApiError extends Error {
  constructor(public status: number, public code: string, message: string, public requestId?: string) {
    super(`${status} ${code}: ${message} (request ${requestId ?? "?"})`);
  }
}

/** GET one /v1 answer. A 4xx/5xx throws ApiError with the envelope's code. */
export async function scan<T = unknown>(path: string, params: Params = {}): Promise<T> {
  const qs = new URLSearchParams();
  for (const [k, v] of Object.entries(params)) if (v !== undefined) qs.set(k, String(v));
  const q = qs.toString();
  const res = await fetch(`${BASE}${path}${q ? `?${q}` : ""}`, {
    headers: { accept: "application/json", "user-agent": "my-integration/1.0" },
  });
  if (!res.ok) {
    const body = (await res.json().catch(() => null)) as { error?: { code: string; message: string; request_id?: string } } | null;
    const e = body?.error ?? { code: String(res.status), message: res.statusText };
    throw new ApiError(res.status, e.code, e.message, e.request_id);
  }
  return (await res.json()) as T;
}

type Networks = { items: { chain_id: number; slug: string; caip2: string; settled: boolean }[] };
const networks = await scan<Networks>("/v1/networks");
for (const n of networks.items) if (n.settled) console.log(n.chain_id, n.slug, n.caip2);
```

세 가지 모두 정산이 인덱싱되는 일곱 개 네트워크를 출력합니다. 하나를 실행해 보면, 이 섹션의 나머지는 경로만 바꾸면 되는 일입니다.

## 다음에 할 일

[내 질문에는 어떤 엔드포인트가 답합니까?](https://achivx.kr/academy/api/which-endpoint-for-my-task/)에서 질문에 맞는 엔드포인트를 고른 다음, [모든 호출이 따르는 규칙](https://achivx.kr/academy/api/the-rules-every-call-follows/)을 한 번 읽으십시오. 무엇보다, `chain`을 받는 모든 호출에 `chain`을 전달하십시오. 잘못된 네트워크에 대한 응답도 겉보기에는 똑같이 응답처럼 보이기 때문입니다.

## Stablecoin scanner ACHIVX에서

- 방금 읽은 [레지스트리](https://achivx.kr/academy/reference/glossary/#g-registry): [/v1/networks](https://stablescan.achivx.com/v1/networks); 네트워크 하나의 스테이블코인: [/v1/tokens?chain=1](https://stablescan.achivx.com/v1/tokens?chain=1).
- 매개변수를 포함한 모든 라우트: [/openapi.json](https://stablescan.achivx.com/openapi.json); 응답의 의미: [/agents.md](https://stablescan.achivx.com/agents.md).

## 출처

- [GET /v1/networks (2026-10-24 조회)](https://stablescan.achivx.com/v1/networks)
- [REST 인터페이스의 OpenAPI 3.1 명세(API 1.4.0)](https://stablescan.achivx.com/openapi.json)
- [에이전트 매뉴얼 — agents.md(버전 2026-10-05)](https://stablescan.achivx.com/agents.md)

## 더 알아보기

- [기계로 사이트 읽기](https://achivx.kr/academy/reference/api-methodology/)
- [REST API는 어떻게 구성되어 있습니까?](https://achivx.kr/academy/for-agents/how-the-rest-api-is-organised/)

## 관련 질문

- [내 질문에는 어떤 엔드포인트가 답합니까?](https://achivx.kr/academy/api/which-endpoint-for-my-task/)
- [모든 호출이 따르는 규칙은 무엇입니까?](https://achivx.kr/academy/api/the-rules-every-call-follows/)
- [코드에서 오류, 요청 한도, 타임아웃은 어떻게 처리합니까?](https://achivx.kr/academy/api/errors-and-limits-in-code/)

여기서 시작하십시오 · 시작 · 2026-10-24 작성, openapi.json, agents.md 및 당일 실제 호출 기준. 응답 형태는 인용이며, 수치는 날짜 기준이고 인덱스에 따라 달라집니다.

---

출처: https://achivx.kr/academy/api/first-request/ · 영어 원문: https://achivx.com/academy/api/first-request.html
