TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import type { Metadata } from "next";2import { CodeBlock } from "@/components/ui/code-block";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 { Endpoint } from "@/components/docs/endpoint";7import { CodeTabs } from "@/components/docs/code-tabs";8import { ResponseExample } from "@/components/docs/response-example";9import { apiTabs } from "@/components/docs/snippets";1011export const metadata: Metadata = {12 title: "Authentication",13 description: "API keys, Bearer and X-API-Key headers, scopes, rotation, revocation and the email verification requirement.",14};1516export default function AuthenticationPage() {17 return (18 <DocPage path="/docs/authentication" title="Authentication" description="Every request to the Fetcha API is authenticated with a project-scoped API key sent in a header. Keys carry scopes, a mode and an optional expiry." status="Stable">19 <H2>API keys</H2>20 <P>21 Keys are created in the dashboard under a project. A key belongs to exactly one project and one organization; the requests it makes are logged and counted against them (Fetcha is a private22 platform with a single unlimited plan, so nothing is billed). The plaintext has a recognisable prefix so you can tell keys apart in configuration:23 </P>24 <Table>25 <THead>26 <Tr>27 <Th>Prefix</Th>28 <Th>Mode</Th>29 <Th>Meaning</Th>30 </Tr>31 </THead>32 <TBody>33 <Tr>34 <Td mono>fch_live_…</Td>35 <Td>live</Td>36 <Td>Production key. Use it in deployed services.</Td>37 </Tr>38 <Tr>39 <Td mono>fch_test_…</Td>40 <Td>test</Td>41 <Td>42 Marked as a test key in logs and in <Code>GET /v1/me</Code>. Behaves like a live key today: requests are real and appear in your usage.43 </Td>44 </Tr>45 </TBody>46 </Table>47 <P>48 A key is between 20 and 128 characters. Anything else, or a token without one of the two prefixes, is rejected with <Code>401 INVALID_API_KEY</Code> before the database is consulted.49 </P>5051 <H3>Shown once, hashed at rest</H3>52 <P>53 The plaintext is displayed a single time when the key is created (or rotated). Fetcha persists only a SHA-256 hash together with the non-secret prefix and last four characters used to54 identify the key in the dashboard. There is no way to retrieve a lost key; rotate it instead.55 </P>5657 <H2>Sending the key</H2>58 <P>Use the standard Bearer scheme. This is what the SDKs send.</P>59 <CodeBlock lang="text" code={`Authorization: Bearer fch_live_…`} />60 <P>61 If your HTTP client cannot set <Code>Authorization</Code>, send the key in <Code>X-API-Key</Code> instead. When both headers are present, <Code>X-API-Key</Code> takes precedence.62 </P>63 <CodeBlock lang="text" code={`X-API-Key: fch_live_…`} />64 <Callout variant="warning" title="Server-side only">65 Never ship a key in a browser bundle or mobile app: anyone who can read it can fetch on your behalf, fill your request log and trip your spending limits. Call Fetcha from your backend. The API allows cross-origin requests so that the dashboard66 can talk to it, not so that keys can live in the browser.67 </Callout>6869 <H2>Scopes</H2>70 <P>71 Each key has a list of scopes chosen at creation. A request that needs a scope the key lacks is refused with <Code>403 FORBIDDEN</Code> and a message naming the missing scope.72 </P>73 <Table>74 <THead>75 <Tr>76 <Th>Scope</Th>77 <Th>Grants</Th>78 <Th>Status</Th>79 </Tr>80 </THead>81 <TBody>82 <Tr>83 <Td mono>fetch:execute</Td>84 <Td>85 <Code>POST /v1/fetch</Code>86 </Td>87 <Td>Live</Td>88 </Tr>89 <Tr>90 <Td mono>sessions:write</Td>91 <Td>92 <Code>POST /v1/sessions</Code>, <Code>DELETE /v1/sessions/:id</Code>. Listing and reading sessions requires no scope.93 </Td>94 <Td>Live</Td>95 </Tr>96 <Tr>97 <Td mono>usage:read</Td>98 <Td>99 <Code>GET /v1/usage</Code>100 </Td>101 <Td>Live</Td>102 </Tr>103 <Tr>104 <Td mono>browser:use</Td>105 <Td>106 Kept for compatibility. The <A href="/docs/browser">managed browser</A> is part of <Code>POST /v1/fetch</Code> and needs only <Code>fetch:execute</Code>.107 </Td>108 <Td>Compatibility</Td>109 </Tr>110 </TBody>111 </Table>112 <P>113 <Code>GET /v1/me</Code> and <Code>GET /v1/sessions</Code> only require a valid key.114 </P>115116 <H2>Accounts and email verification</H2>117 <P>118 Fetcha is invitation-only: an account can only be created with an email address that a Fetcha administrator placed on the access list. Once the account exists, API keys work only after119 that email address is verified. Until then every authenticated call, including <Code>/v1/me</Code>, fails with <Code>403 EMAIL_NOT_VERIFIED</Code>. Resend the verification email from the120 dashboard if you did not receive it. The dashboard Playground is exempt so you can try the product before verifying.121 </P>122 <ResponseExample123 status={403}124 statusText="Forbidden"125 body={{ error: { code: "EMAIL_NOT_VERIFIED", message: "Verify your email address before using the production API.", request_id: "req_7f2c1a9e0d4b6h3k" } }}126 />127128 <H2>Rotation and revocation</H2>129 <Ul>130 <Li>131 <Strong>Rotate</Strong> replaces the secret of an existing key and shows the new plaintext once. The old plaintext stops working immediately.132 </Li>133 <Li>134 <Strong>Revoke</Strong> disables a key permanently. Requests made with it return <Code>401 INVALID_API_KEY</Code> with the message <Code>This API key was revoked.</Code>135 </Li>136 <Li>137 <Strong>Expiry</Strong> is optional and set in days at creation. Expired keys return <Code>401 INVALID_API_KEY</Code> with <Code>This API key has expired.</Code>138 </Li>139 </Ul>140 <P>141 Authenticated principals are cached for up to 30 seconds per API process. The dashboard invalidates the cache when you rotate or revoke, so changes normally apply at once; in the rare case142 the invalidation is lost, a stale key stops working within 30 seconds.143 </P>144 <P>145 Other conditions that block a key even when it is valid: the project was archived (<Code>403 FORBIDDEN</Code>), the organization was suspended (<Code>403 FORBIDDEN</Code>, contact{" "}146 <A href="mailto:support@fetcha.co">support@fetcha.co</A>).147 </P>148149 <H2>Checking a key</H2>150 <P>151 <Code>GET /v1/me</Code> validates the key and returns the project, organization and key metadata it resolves to. It is the cheapest call you can make and a good health check for your152 configuration.153 </P>154 <Endpoint method="GET" path="/v1/me" scope={null} status="Live" />155 <CodeTabs tabs={apiTabs({ method: "GET", path: "/v1/me" })} />156 <ResponseExample157 status={200}158 body={{159 project: { id: "proj_2k8d1m3p9q4r7s6t", name: "Default" },160 organization: { id: "org_9a1b2c3d4e5f6g7h", name: "Acme Data", plan: "unlimited" },161 key: { id: "key_5t6y7u8i9o0p1a2s", name: "backend", mode: "live", scopes: ["fetch:execute", "sessions:write", "usage:read"] },162 }}163 />164165 <H2>Logging and redaction</H2>166 <P>167 Fetcha never logs your API key. On the request log, <Code>Authorization</Code>, <Code>Cookie</Code>, <Code>Set-Cookie</Code>, <Code>X-API-Key</Code> and similar headers that you send to168 targets are stored as <Code>[redacted]</Code> when your project log level includes headers. Response bodies are not stored by default.169 </P>170 </DocPage>171 );172}173