> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anchorage.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API keys

> Generate, use, revoke, and rotate API access keys to authenticate requests to the Anchorage Digital API.

export const AnnotatedImage = ({src, alt = "", annotations = [], width, style = {}}) => {
  const [active, setActive] = useState(null);
  const containerStyle = {
    position: "relative",
    display: "block",
    width: width ?? "100%",
    ...style
  };
  const imgStyle = {
    display: "block",
    width: "100%",
    height: "auto",
    borderRadius: "8px"
  };
  return <div style={containerStyle}>
      <img src={src} alt={alt} style={imgStyle} />

      {annotations.map((ann, i) => {
    const isActive = active === i;
    const color = ann.color ?? "#e53e3e";
    const rectStyle = {
      position: "absolute",
      left: ann.x + "%",
      top: ann.y + "%",
      width: ann.width + "%",
      height: ann.height + "%",
      border: "2px solid " + color,
      background: isActive ? color + "26" : color + "12",
      boxShadow: isActive ? "0 0 0 1px " + color + "59, inset 0 0 0 1px " + color + "33" : "none",
      borderRadius: "3px",
      cursor: "default",
      transition: "background 120ms ease, box-shadow 120ms ease",
      zIndex: isActive ? 100 : 10,
      boxSizing: "border-box"
    };
    const flipAbove = ann.y + ann.height > 60;
    const flipLeft = ann.x > 50;
    const bubbleStyle = {
      position: "absolute",
      ...flipAbove ? {
        bottom: "calc(100% + 8px)"
      } : {
        top: "calc(100% + 8px)"
      },
      ...flipLeft ? {
        right: 0
      } : {
        left: 0
      },
      minWidth: "180px",
      maxWidth: "260px",
      padding: "10px 13px",
      background: "var(--background, #fff)",
      border: "1px solid var(--ad-border, #e4e5e7)",
      borderRadius: "10px",
      boxShadow: "0 8px 24px rgba(20,20,21,0.12)",
      pointerEvents: "none",
      zIndex: 9999,
      opacity: isActive ? 1 : 0,
      transition: "opacity 120ms ease",
      whiteSpace: "normal"
    };
    return <div key={i} style={rectStyle} onMouseEnter={() => setActive(i)} onMouseLeave={() => setActive(null)}>
            <div style={bubbleStyle} role="tooltip">
              {ann.label && <div style={{
      fontSize: "13px",
      fontWeight: 700,
      marginBottom: ann.description ? "4px" : 0,
      color
    }}>
                  {ann.label}
                </div>}
              {ann.description && <div style={{
      fontSize: "12.5px",
      lineHeight: 1.45,
      opacity: 0.7
    }}>
                  {ann.description}
                </div>}
            </div>
          </div>;
  })}
    </div>;
};

export const KeyPairGenerator = () => {
  const [signingKey, setSigningKey] = useState("");
  const [publicKey, setPublicKey] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const [copied, setCopied] = useState({});
  const bytesToHex = bytes => Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
  const generateKeys = async () => {
    setLoading(true);
    setError("");
    setSigningKey("");
    setPublicKey("");
    try {
      const keyPair = await crypto.subtle.generateKey({
        name: "Ed25519"
      }, true, ["sign", "verify"]);
      const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([crypto.subtle.exportKey("pkcs8", keyPair.privateKey), crypto.subtle.exportKey("spki", keyPair.publicKey)]);
      const rawSeed = new Uint8Array(privateKeyBuffer).slice(16);
      const rawPublicKey = new Uint8Array(publicKeyBuffer).slice(12);
      setSigningKey(bytesToHex(rawSeed));
      setPublicKey(bytesToHex(rawPublicKey));
    } catch (err) {
      setError("Key generation failed. Your browser may not support Ed25519 (requires Chrome 113+, Firefox 130+, or Safari 17+).");
    } finally {
      setLoading(false);
    }
  };
  const copyToClipboard = async (text, id) => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(prev => ({
        ...prev,
        [id]: true
      }));
      setTimeout(() => setCopied(prev => ({
        ...prev,
        [id]: false
      })), 2000);
    } catch {}
  };
  const fieldStyle = {
    fontFamily: "monospace",
    fontSize: "12px",
    background: "var(--background-color, #f4f5f5)",
    border: "1px solid var(--border-color, #e5e7eb)",
    borderRadius: "6px",
    padding: "10px 12px",
    wordBreak: "break-all",
    color: "var(--text-color, #111)",
    minHeight: "40px"
  };
  const labelStyle = {
    fontSize: "12px",
    fontWeight: 600,
    marginBottom: "6px",
    color: "var(--muted-text-color, #6b7280)",
    textTransform: "uppercase",
    letterSpacing: "0.05em"
  };
  const copyBtnStyle = active => ({
    marginTop: "6px",
    padding: "4px 10px",
    fontSize: "12px",
    borderRadius: "5px",
    border: "1px solid var(--border-color, #e5e7eb)",
    background: active ? "#16a34a" : "transparent",
    color: active ? "#fff" : "var(--text-color, #374151)",
    cursor: "pointer",
    transition: "background 0.15s, color 0.15s"
  });
  const generateBtnStyle = {
    padding: "9px 18px",
    fontSize: "14px",
    fontWeight: 600,
    borderRadius: "7px",
    border: "none",
    background: loading ? "#93c5fd" : "#3b82f6",
    color: "#fff",
    cursor: loading ? "default" : "pointer",
    transition: "background 0.15s"
  };
  const wrapperStyle = {
    borderRadius: "10px",
    border: "1px solid var(--border-color, #e5e7eb)",
    padding: "20px 24px",
    maxWidth: "680px",
    display: "flex",
    flexDirection: "column",
    gap: "16px"
  };
  return <div style={wrapperStyle}>
      <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
        <div style={{
    fontWeight: 700,
    fontSize: "15px"
  }}>Ed25519 key pair generator</div>
        <div style={{
    fontSize: "13px",
    color: "var(--muted-text-color, #6b7280)"
  }}>
          Generate a signing key and public key for use with the Anchorage Digital API. Keys are generated locally in your browser — never transmitted.
        </div>
      </div>

      <button style={generateBtnStyle} onClick={generateKeys} disabled={loading}>
        {loading ? "Generating…" : "Generate key pair"}
      </button>

      {error && <div style={{
    fontSize: "13px",
    color: "#dc2626",
    padding: "8px 12px",
    background: "#fef2f2",
    borderRadius: "6px",
    border: "1px solid #fecaca"
  }}>
          {error}
        </div>}

      {(signingKey || publicKey) && <>
          <div>
            <div style={labelStyle}>Signing key (private)</div>
            <div style={fieldStyle}>{signingKey || "—"}</div>
            <div style={{
    display: "flex",
    gap: "6px",
    marginTop: "6px",
    alignItems: "center"
  }}>
              <button style={copyBtnStyle(copied["signing"])} onClick={() => copyToClipboard(signingKey, "signing")}>
                {copied["signing"] ? "Copied!" : "Copy"}
              </button>
              <span style={{
    fontSize: "11px",
    color: "#dc2626",
    fontWeight: 500
  }}>
                ⚠ Store this securely — never share it.
              </span>
            </div>
          </div>

          <div>
            <div style={labelStyle}>Public key (verify key)</div>
            <div style={fieldStyle}>{publicKey || "—"}</div>
            <div style={{
    display: "flex",
    gap: "6px",
    marginTop: "6px",
    alignItems: "center"
  }}>
              <button style={copyBtnStyle(copied["public"])} onClick={() => copyToClipboard(publicKey, "public")}>
                {copied["public"] ? "Copied!" : "Copy"}
              </button>
              <span style={{
    fontSize: "11px",
    color: "var(--muted-text-color, #6b7280)"
  }}>
                Paste this into the Anchorage Digital web dashboard when creating an API key.
              </span>
            </div>
          </div>
        </>}
    </div>;
};

An API key authenticates your requests to the Anchorage Digital API. Each key is scoped to a [permission group](/knowledge-base/platform/developers/permission-groups) that defines what it can do, so create the group you need before you generate the key.

## How to generate an API key

<Steps>
  <Step title="Navigate to API key creation">
    In the **Developers**, then **API 2.0** section of the web dashboard, select **Create API key**.

    <AnnotatedImage
      src="/images/screenshots/api-keys.png"
      alt="Dashboard"
      annotations={[
    { x: 1, y: 75, width: 15, height: 20, label: "API configuration", description: "Navigate to the API section" },
    { x: 88, y: 30, width: 10, height: 8, label: "Create API key", description: "Recent transaction log" },
  ]}
    />
  </Step>

  <Step title="Generate an Ed25519 key pair">
    The web dashboard supports in-app key generation, or you can bring your own keys. Use the widget below to generate a key pair in this browser session, or run the Python script manually.

    <KeyPairGenerator />

    <Expandable title="Python script">
      ```python theme={null}
      # https://pypi.org/project/PyNaCl/

      import nacl
      import nacl.signing
      import secrets

      seed = secrets.token_bytes(32)

      signing_key = nacl.signing.SigningKey(seed)

      print('Signing key:')
      print(signing_key.encode().hex())

      print('Public key:')
      print(signing_key.verify_key.encode().hex())
      ```
    </Expandable>
  </Step>

  <Step title="Create the API key">
    Select the permission group you created, and use the public key from the previous step to create your API key. Selecting certain permission groups requires additional quorum approval before the key can be used.

    <Warning>
      Copy the API key immediately — it isn't displayed again, and keys aren't recoverable. If you lose a key, revoke it and generate a new one.
    </Warning>
  </Step>
</Steps>

When you create a key, you'll work with three values:

1. **API access key** — sent on every request as `Api-Access-Key`.
2. **Ed25519 public key** — registered during key generation. Use the Anchorage Digital-generated one or bring your own.
3. **Ed25519 private signing key** — kept locally to produce signatures before a call.

## Using an API key

Every request is made over HTTPS and includes your access key in the `Api-Access-Key` header.

```bash theme={null}
curl --request GET \
  --url https://api.anchorage-staging.com/v2/apikey \
  --header 'Api-Access-Key: <YOUR_API_KEY>'
```

Sensitive endpoints also require a request signature, passed as `Api-Timestamp` and `Api-Signature`.

```bash theme={null}
curl --request POST \
  --url https://api.anchorage-staging.com/v2/transactions/withdrawal \
  --header 'Api-Access-Key: <YOUR_API_KEY>' \
  --header 'Api-Signature: <API_SIGNATURE>' \
  --header 'Api-Timestamp: <TIMESTAMP>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "amount": "1000.00000000",
  "assetType": "BTC",
  "description": "Internal ID: #12838927347",
  "idempotentId": "<SOME_UUID>",
  "useGasStation": true
}
'
```

See [Signing requests](/knowledge-base/platform/developers/request-signing) for the full signature recipe before making sensitive calls.

## Revoking an API key

To revoke a single key, select the three-dot menu next to the key and select **Revoke**. To revoke every key in a group at once, delete the [permission group](/knowledge-base/platform/developers/permission-groups) the keys belong to.

## Rotating API keys

There's no dedicated rotation endpoint. You rotate a key yourself by revoking the active key and creating a new one tied to the permission group you want.

<Steps>
  <Step title="Create the replacement key">
    Generate a new API key and assign it to the desired permission group, following the steps above. Update your integration to use the new key.
  </Step>

  <Step title="Revoke the old key">
    Once the new key is in place and confirmed working, revoke the previous key.
  </Step>
</Steps>

<Tip>
  Rotate keys on a regular schedule, and immediately if a key may have been exposed. Creating the replacement before revoking the old key avoids any gap in access.
</Tip>
