Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/cli-docs/src/fragments/commands/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

Endpoints are relative to `/api/0/` — the prefix is added automatically. Absolute HTTP(S) Sentry URLs also work; their origin is validated against your authenticated host.

A relative endpoint that names an organization, such as
`organizations/acme-corp/issues/` or `projects/acme-corp/web/`, is sent to that
organization's region (for example `https://us.sentry.io`) with the stored
account that knows it, as other commands do. The first request for an
organization may look up its region, and `--dry-run` shows the URL that would be
used. An absolute URL is sent as given.

If a successful GET response is interrupted while downloading, the CLI retries
within its existing limit of three attempts. This also applies to complete API
responses read by other commands. The request timeout covers these response
Expand Down
18 changes: 9 additions & 9 deletions packages/cli/src/lib/api/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export async function getIssueAlertWorkflowDocument(
orgSlug: string,
workflowId: string,
): Promise<GetOrganizationWorkflowResponse> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await getOrganizationWorkflow({
...config,
path: {
Expand All @@ -328,7 +328,7 @@ export async function resolveErrorDetectorId(
orgSlug: string,
projectSlug: string,
): Promise<number> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await listOrganizationDetectors({
...config,
path: { organization_id_or_slug: orgSlug },
Expand Down Expand Up @@ -413,7 +413,7 @@ export async function listMetricAlertsPaginated(
orgSlug: string,
options: { perPage?: number; cursor?: string } = {},
): Promise<PaginatedResponse<MetricAlertRule[]>> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await listOrganizationDetectors({
...config,
path: { organization_id_or_slug: orgSlug },
Expand Down Expand Up @@ -470,7 +470,7 @@ async function getMetricDetector(
orgSlug: string,
ruleId: string,
): Promise<MetricDetector> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await getOrganizationDetector({
...config,
// The SDK types detector_id as number, but detector IDs are opaque strings
Expand Down Expand Up @@ -532,7 +532,7 @@ export async function updateIssueAlertRule(
workflowId: string,
body: Record<string, unknown>,
): Promise<UpdateOrganizationWorkflowResponse> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await updateOrganizationWorkflow({
...config,
path: {
Expand Down Expand Up @@ -562,7 +562,7 @@ export async function createIssueAlertRule(
orgSlug: string,
body: Record<string, unknown>,
): Promise<Record<string, unknown>> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await createOrganizationWorkflow({
...config,
path: { organization_id_or_slug: orgSlug },
Expand Down Expand Up @@ -768,7 +768,7 @@ export async function deleteMetricAlertRule(
orgSlug: string,
ruleId: string,
): Promise<void> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await deleteOrganizationDetector({
...config,
// Detector IDs are opaque strings the API accepts verbatim; the SDK types
Expand Down Expand Up @@ -828,7 +828,7 @@ export async function putMetricAlertRule(
ruleId: string,
body: Record<string, unknown>,
): Promise<UpdateOrganizationDetectorResponse> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await updateOrganizationDetector({
...config,
path: {
Expand Down Expand Up @@ -861,7 +861,7 @@ export async function createMetricAlertRule(
body: Record<string, unknown>,
): Promise<Record<string, unknown>> {
const projectSlug = resolveDetectorProject(body);
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await createOrganizationProjectDetector({
...config,
path: {
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/lib/api/dashboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function listDashboardsPaginated(
orgSlug: string,
options: { perPage?: number; cursor?: string } = {},
): Promise<PaginatedResponse<DashboardListItem[]>> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await listOrganizationDashboards({
...config,
Expand All @@ -89,7 +89,7 @@ export async function getDashboard(
orgSlug: string,
dashboardId: string,
): Promise<DashboardDetail> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await getOrganizationDashboard({
...config,
Expand All @@ -116,7 +116,7 @@ export async function createDashboard(
orgSlug: string,
body: { title: string; widgets?: DashboardWidget[]; projects?: number[] },
): Promise<DashboardDetail> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await createOrganizationDashboard({
...config,
Expand Down Expand Up @@ -149,7 +149,7 @@ export async function updateDashboard(
period?: string | null;
},
): Promise<DashboardDetail> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await updateOrganizationDashboard({
...config,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/lib/api/dropped-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function getDroppedEvents(
orgSlug: string,
options: DroppedEventsOptions = {},
): Promise<ListOrganizationEventsDroppedResponse> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await listOrganizationEventsDropped({
...config,
path: { organization_id_or_slug: orgSlug },
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/lib/api/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function getLatestEvent(
orgSlug: string,
issueId: string,
): Promise<SentryEvent> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await getOrganizationIssueEvent({
...config,
Expand All @@ -65,7 +65,7 @@ export async function getEvent(
projectSlug: string,
eventId: string,
): Promise<SentryEvent> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await getProjectEvent({
...config,
Expand All @@ -90,7 +90,7 @@ export async function listEventAttachments(
projectSlug: string,
eventId: string,
): Promise<EventAttachmentDetailsResponse[]> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const { data } = await autoPaginate(
async (cursor) => {
// The endpoint accepts `per_page`, but its generated SDK type omits it.
Expand Down Expand Up @@ -140,7 +140,7 @@ export async function resolveEventInOrg(
orgSlug: string,
eventId: string,
): Promise<ResolvedEvent | null> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await sdkResolveAnEventId({
...config,
Expand Down Expand Up @@ -259,7 +259,7 @@ export async function listIssueEvents(
): Promise<PaginatedResponse<IssueEvent[]>> {
const { limit = 25, query, full, cursor, statsPeriod, start, end } = options;

const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

return await paginate({ limit, cursor }, async (perPage, pageCursor) => {
const result = await listOrganizationIssueEvents({
Expand Down
36 changes: 29 additions & 7 deletions packages/cli/src/lib/api/infrastructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { promisify } from "node:util";
import { zstdCompress as zstdCompressCb } from "node:zlib";
import {
type BaseUrlRequest,
createSentryClient,
parseSentryLinkHeader,
type SdkResult,
Expand Down Expand Up @@ -520,12 +521,27 @@ export function appendSearchParams(
}

/**
* Get SDK config for an organization's region.
* Resolves the org's region URL and returns the config.
* `resolveBaseUrl` for `@sentry/api` clients: a request whose path names an
* organization goes to that organization's region, on the server of the
* stored account that knows it ({@link resolveOrgRegion}). An explicit
* server still limits the accounts. Unscoped paths keep the base URL.
*/
export async function getOrgSdkConfig(orgSlug: string) {
const regionUrl = await resolveOrgRegion(orgSlug);
return getSdkClientConfig(regionUrl);
async function resolveOrgBaseUrl({
baseUrl,
scope,
}: BaseUrlRequest): Promise<string> {
return scope ? await resolveOrgRegion(scope.organization) : baseUrl;
}

/**
* SDK config that sends each request to the region of the organization its
* path names ({@link resolveOrgBaseUrl}).
*/
export function getOrgSdkConfig() {
return {
...getSdkClientConfig(getApiBaseUrl()),
resolveBaseUrl: resolveOrgBaseUrl,
};
}

/**
Expand Down Expand Up @@ -998,7 +1014,8 @@ export type PreparedRawApiRequest = {
};

/**
* Prepare a raw request without fetching or refreshing credentials.
* Prepare a raw request without sending it or refreshing credentials; an
* org-scoped path may look up the organization's region first.
* Existing params retain append semantics; query overrides use the SDK's
* replacement semantics for decoded page cursors and page sizes.
*/
Expand All @@ -1008,9 +1025,14 @@ export async function prepareRawApiRequest(
query?: Record<string, unknown>,
): Promise<PreparedRawApiRequest> {
const { method = "GET", body, params, headers, baseUrl } = options;
// A full URL keeps the request on its server; otherwise the path's
// organization picks the account, server, and region.
const config = baseUrl
? getSdkConfig(baseUrl, { bufferResponseBody: true })
: getDefaultSdkConfig({ bufferResponseBody: true, path: endpoint });
: {
...getDefaultSdkConfig({ bufferResponseBody: true }),
resolveBaseUrl: resolveOrgBaseUrl,
};
const isStringBody = typeof body === "string";
const request = await createSentryClient(config).client.prepareRequest({
method,
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/lib/api/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function listIntegrationsPaginated(
orgSlug: string,
options: { cursor?: string; perPage?: number } = {},
): Promise<PaginatedResponse<SentryIntegration[]>> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await listOrganizationIntegrations({
...config,
Expand Down Expand Up @@ -100,7 +100,7 @@ export async function getIntegration(
orgSlug: string,
integrationId: string,
): Promise<SentryIntegration> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await getOrganizationIntegration({
...config,
Expand All @@ -127,7 +127,7 @@ export async function deleteIntegration(
orgSlug: string,
integrationId: string,
): Promise<void> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await deleteOrganizationIntegration({
...config,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/lib/api/issue-tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function getIssueTag(
key: string,
environment?: string,
): Promise<GetOrganizationIssueTagResponse> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const result = await getOrganizationIssueTag({
...config,
path: {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/lib/api/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export async function listIssuesPaginated(
projects === undefined && projectSlug ? `project:${projectSlug}` : "";
const fullQuery = [projectFilter, options.query].filter(Boolean).join(" ");

const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await listOrganizationIssues({
...config,
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/lib/api/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export async function listLogs(
.filter(Boolean)
.join(" ");

const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

// Merge extra fields (from --fields) with the default set, deduplicating
const fields = options.extraFields?.length
Expand Down Expand Up @@ -304,7 +304,7 @@ function getLogLookupWindow(
* Batch size must not exceed {@link API_MAX_PER_PAGE}.
*/
type GetLogsBatchOptions = {
config: Awaited<ReturnType<typeof getOrgSdkConfig>>;
config: ReturnType<typeof getOrgSdkConfig>;
extraFields?: string[];
/** Numeric project ID for direct project selection. @see {@link ListLogsOptions.projectId} */
projectId?: number;
Expand Down Expand Up @@ -415,7 +415,7 @@ export async function getLogs(
options: GetLogsOptions = {},
): Promise<DetailedSentryLog[]> {
const { extraFields, projectId } = options;
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();
const batchOptions: GetLogsBatchOptions = { config, extraFields, projectId };

// Single batch — no splitting needed
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/lib/api/monitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
* @returns Monitors, including nested monitor environments
*/
export async function listMonitors(orgSlug: string): Promise<SentryMonitor[]> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const { data: allResults } = await autoPaginate(async (cursor) => {
const result = await listOrganizationMonitors({
Expand Down Expand Up @@ -63,7 +63,7 @@ export async function listMonitorsPaginated(
orgSlug: string,
options: { cursor?: string; perPage?: number; query?: string } = {},
): Promise<PaginatedResponse<SentryMonitor[]>> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await listOrganizationMonitors({
...config,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/lib/api/organizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export async function listOrganizationsUncached(
export async function getOrganization(
orgSlug: string,
): Promise<SentryOrganization> {
const config = await getOrgSdkConfig(orgSlug);
const config = getOrgSdkConfig();

const result = await sdkGetOrganization({
...config,
Expand Down
Loading
Loading