SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
7.7 KB · 163 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import { COUNTRIES } from "@fetcha/core";3import { DocPage } from "@/components/docs/doc-page";4import { A, Code, H2, H3, Li, P, Strong, Table, TBody, Td, Th, THead, Tr, Ul } from "@/components/docs/prose";5import { Callout } from "@/components/docs/callout";6import { CodeTabs } from "@/components/docs/code-tabs";7import { ParamTable } from "@/components/docs/param-table";8import { ResponseExample } from "@/components/docs/response-example";9import { fetchTabs } from "@/components/docs/snippets";1011export const metadata: Metadata = {12  title: "Geolocation",13  description: "Target a country, region and city for the exit IP: accepted values, normalisation rules and behaviour when a location is unavailable.",14};1516const CA_REGIONS: Array<[string, string, string]> = [17  ["QC", "Quebec", "quebec"],18  ["ON", "Ontario", "ontario"],19  ["BC", "British Columbia", "british_columbia"],20  ["AB", "Alberta", "alberta"],21  ["MB", "Manitoba", "manitoba"],22  ["SK", "Saskatchewan", "saskatchewan"],23  ["NS", "Nova Scotia", "nova_scotia"],24  ["NB", "New Brunswick", "new_brunswick"],25  ["NL", "Newfoundland and Labrador", "newfoundland_and_labrador"],26  ["PE / PEI", "Prince Edward Island", "prince_edward_island"],27  ["YT", "Yukon", "yukon"],28  ["NT", "Northwest Territories", "northwest_territories"],29  ["NU", "Nunavut", "nunavut"],30];3132const US_SAMPLE: Array<[string, string]> = [33  ["CA", "california"],34  ["NY", "new_york"],35  ["TX", "texas"],36  ["FL", "florida"],37  ["WA", "washington"],38  ["IL", "illinois"],39  ["MA", "massachusetts"],40  ["NJ", "new_jersey"],41  ["NC", "north_carolina"],42  ["DC", "district_of_columbia"],43];4445export default function GeolocationPage() {46  const countries = Object.entries(COUNTRIES);47  return (48    <DocPage path="/docs/geolocation" eyebrow="Core API" title="Geolocation" description="Choose where your request appears to come from. Country is the primary control; region and city refine it where the network has coverage." status="Stable">49      <H2>Fields</H2>50      <P>51        The three geography fields are shared by <Code>POST /v1/fetch</Code> and <Code>POST /v1/sessions</Code>. All are optional. When none is set, the network picks any exit and{" "}52        <Code>metadata.country</Code> is <Code>null</Code>.53      </P>54      <ParamTable55        showDefault={false}56        rows={[57          { name: "country", type: "string", constraints: "exactly 2 characters", description: <>ISO 3166-1 alpha-2 code. Case-insensitive; normalised to upper case (<code>{`"ca"`}</code> → <code>{`"CA"`}</code>). Any other length fails validation with <code>INVALID_REQUEST</code>.</> },58          { name: "region", type: "string", constraints: "≤ 64 characters", description: <>State, province or territory. For <code>US</code> and <code>CA</code>, two-letter postal codes and full names are recognised and normalised to a canonical slug. For every other country the value is slugified as-is.</> },59          { name: "city", type: "string", constraints: "≤ 128 characters", description: <>City name, slugified: accents removed, lower-cased, non-alphanumeric runs replaced by <code>_</code>. <code>{`"Québec"`}</code> → <code>quebec</code>, <code>{`"Saint-Jean-sur-Richelieu"`}</code> → <code>saint_jean_sur_richelieu</code>.</> },60        ]}61      />6263      <H2>Example</H2>64      <P>Fetch through a residential exit in Québec City, Canada.</P>65      <CodeTabs66        tabs={fetchTabs(67          { url: "https://www.example.ca/", country: "CA", region: "QC", city: "Quebec" },68          {69            javascript: `console.log(data.metadata.country); // "CA"`,70            python: `print(data["metadata"]["country"])  # "CA"`,71          },72        )}73      />74      <ResponseExample75        status={200}76        title="200 OK · metadata excerpt"77        body={{ metadata: { network: "residential", country: "CA", attempts: 1, duration_ms: 1290, bytes: 61233, session: null, cached: false } }}78      />7980      <H2>Normalisation</H2>81      <P>82        Fetcha converts what you send into a canonical internal form before handing it to the network, so the same location can be written several ways. The tables below show the recognised83        aliases for Canada and a sample for the United States; every US state, plus <Code>DC</Code>, is supported.84      </P>85      <H3>Canadian provinces and territories</H3>86      <Table dense>87        <THead>88          <Tr>89            <Th>Code</Th>90            <Th>Name</Th>91            <Th>Normalised value</Th>92          </Tr>93        </THead>94        <TBody>95          {CA_REGIONS.map(([code, name, slug]) => (96            <Tr key={slug}>97              <Td mono>{code}</Td>98              <Td>{name}</Td>99              <Td mono>{slug}</Td>100            </Tr>101          ))}102        </TBody>103      </Table>104      <H3>US states (sample)</H3>105      <Table dense>106        <THead>107          <Tr>108            <Th>Code</Th>109            <Th>Normalised value</Th>110          </Tr>111        </THead>112        <TBody>113          {US_SAMPLE.map(([code, slug]) => (114            <Tr key={code}>115              <Td mono>{code}</Td>116              <Td mono>{slug}</Td>117            </Tr>118          ))}119        </TBody>120      </Table>121      <P>122        Full names are accepted for both countries (<Code>{`"Ontario"`}</Code>, <Code>{`"British Columbia"`}</Code>, <Code>{`"Québec"`}</Code>). Only the two-letter postal code is accepted for US states; full US state123        names fall through to plain slugification, which still yields the same slug for single-word states (<Code>{`"texas"`}</Code>) but not for multi-word ones.124      </P>125126      <H2>Countries</H2>127      <P>128        The API accepts any two-letter code. The following {countries.length} countries are the ones surfaced in the dashboard pickers; coverage of others depends on the network and is best confirmed129        with a test request.130      </P>131      <div className="my-5 grid grid-cols-2 gap-x-6 gap-y-1 rounded-lg border border-border p-4 text-[13px] sm:grid-cols-3">132        {countries.map(([code, name]) => (133          <div key={code} className="flex items-baseline gap-2">134            <code className="font-mono text-[12px] text-fg">{code}</code>135            <span className="truncate text-fg-muted">{name}</span>136          </div>137        ))}138      </div>139140      <H2>When a location is unavailable</H2>141      <Ul>142        <Li>143          <Strong>No route can serve the geography.</Strong> If none of the eligible routes supports the requested country, the request fails before any attempt: <Code>503 PROVIDER_UNAVAILABLE</Code>{" "}144          in <Code>auto</Code> mode, <Code>400 NETWORK_UNAVAILABLE</Code> with an explicit class. For sessions, creation fails with <Code>NETWORK_UNAVAILABLE</Code>.145        </Li>146        <Li>147          <Strong>Region or city has thin coverage.</Strong> Country is the level the routing engine guarantees when it selects a route. Region and city are forwarded to the network as targeting148          hints; the network serves the closest exit it has. Fetcha does not currently verify the exit location against an independent geo-IP database, so if the exact city matters to your use149          case, confirm it with a request to a geo-echo endpoint of your choice.150        </Li>151        <Li>152          <Strong>Sessions inherit geography.</Strong> When a fetch carries a <Code>session</Code> and omits <Code>country</Code>, the session&apos;s country is used. See{" "}153          <A href="/docs/sessions">Sessions</A>.154        </Li>155      </Ul>156      <Callout variant="info" title="metadata.country echoes your request">157        <Code>metadata.country</Code> reports the country that was targeted for the final attempt (yours or the session&apos;s), not a measured location. It is <Code>null</Code> when you did not ask158        for one.159      </Callout>160    </DocPage>161  );162}163