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

# Authentication

> How to authenticate with the Zodia Custody API

## Key Pairs Required

Two sets of RSA/ECC key pairs are needed before making any API calls.

### 1. Company Key Pair (RSA)

Used to authenticate all API requests. Generated once per company.

* `company_pri_key` — private key (never share)
* `company_pub_key` — share with Zodia at **[customerservice@zodia.io](mailto:customerservice@zodia.io)**

Keys must be **RSA**, base64-encoded, minimum **2048 bits**.

<CodeGroup>
  ```python Python theme={null}
  from cryptography.hazmat.backends import default_backend
  from cryptography.hazmat.primitives import serialization
  from cryptography.hazmat.primitives.asymmetric import rsa
  import os

  def generate_rsa_keypair(company):
      rsa_private_key = rsa.generate_private_key(
          public_exponent=65537, key_size=2048, backend=default_backend()
      )
      private_key_pem = rsa_private_key.private_bytes(
          serialization.Encoding.PEM,
          serialization.PrivateFormat.PKCS8,
          serialization.NoEncryption()
      )
      with open(os.path.join("keys", company + ".private.pem"), "wb") as f:
          f.write(private_key_pem)

      public_key_pem = rsa_private_key.public_key().public_bytes(
          serialization.Encoding.PEM,
          serialization.PublicFormat.SubjectPublicKeyInfo
      )
      with open(os.path.join("keys", company + ".public.pem"), "wb") as f:
          f.write(public_key_pem)

  generate_rsa_keypair("ZTEST")
  ```

  ```javascript JavaScript theme={null}
  const { generateKeyPairSync } = require('crypto');
  const fs = require('fs');
  const path = require('path');

  function generateRsaKeypair(company) {
      const { privateKey, publicKey } = generateKeyPairSync('rsa', {
          modulusLength: 2048,
          publicKeyEncoding: { type: 'spki', format: 'pem' },
          privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
      });

      fs.writeFileSync(path.join('keys', `${company}.private.pem`), privateKey);
      fs.writeFileSync(path.join('keys', `${company}.public.pem`), publicKey);
  }

  generateRsaKeypair('ZTEST');
  ```

  ```java Java theme={null}
  import java.security.*;
  import java.io.*;
  import java.util.Base64;

  public class GenerateRsaKeypair {
      public static void generateRsaKeypair(String company) throws Exception {
          KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
          keyGen.initialize(2048);
          KeyPair keyPair = keyGen.generateKeyPair();

          byte[] privateBytes = keyPair.getPrivate().getEncoded();
          String privateKey = "-----BEGIN PRIVATE KEY-----\n"
              + Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(privateBytes)
              + "\n-----END PRIVATE KEY-----\n";
          try (FileWriter fw = new FileWriter("keys/" + company + ".private.pem")) {
              fw.write(privateKey);
          }

          byte[] publicBytes = keyPair.getPublic().getEncoded();
          String publicKey = "-----BEGIN PUBLIC KEY-----\n"
              + Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(publicBytes)
              + "\n-----END PUBLIC KEY-----\n";
          try (FileWriter fw = new FileWriter("keys/" + company + ".public.pem")) {
              fw.write(publicKey);
          }
      }

      public static void main(String[] args) throws Exception {
          generateRsaKeypair("ZTEST");
      }
  }
  ```

  ```go Go theme={null}
  package main

  import (
      "crypto/rand"
      "crypto/rsa"
      "crypto/x509"
      "encoding/pem"
      "os"
      "path/filepath"
  )

  func generateRsaKeypair(company string) error {
      privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
      if err != nil {
          return err
      }

      privateFile, err := os.Create(filepath.Join("keys", company+".private.pem"))
      if err != nil {
          return err
      }
      defer privateFile.Close()
      pem.Encode(privateFile, &pem.Block{
          Type:  "PRIVATE KEY",
          Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
      })

      publicFile, err := os.Create(filepath.Join("keys", company+".public.pem"))
      if err != nil {
          return err
      }
      defer publicFile.Close()
      pubBytes, _ := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
      pem.Encode(publicFile, &pem.Block{
          Type:  "PUBLIC KEY",
          Bytes: pubBytes,
      })

      return nil
  }

  func main() {
      generateRsaKeypair("ZTEST")
  }
  ```

  ```shell Shell theme={null}
  mkdir -p keys
  openssl genrsa -out keys/ZTEST.private.pem 2048
  openssl rsa -in keys/ZTEST.private.pem -pubout -out keys/ZTEST.public.pem
  ```
</CodeGroup>

### 2. User Key Pair (ECC)

Each API user (maker and checker) needs an **Elliptic Curve** key pair using **SECP256R1**.

```shell theme={null}
openssl ecparam -name prime256v1 -genkey -noout -out ./private-key.pem
openssl ec -in ./private-key.pem -pubout -out ./public-key.pem
```

Share with Zodia: the user's email, ECC public key, and assigned roles (Viewer, Maker, Checker).

## Signature Types

Two distinct signature types are used in the Zodia API:

<img src="https://mintcdn.com/zodiacustody/RKAOyMi_osGFK65R/img/signature-explained.png?fit=max&auto=format&n=RKAOyMi_osGFK65R&q=85&s=39952412ca473ec7d242da0e69839011" alt="Signature types explained" width="2935" height="2475" data-path="img/signature-explained.png" />

## Onboarding Checklist

1. Generate company RSA key pair
2. Send `company_pub_key` to [customerservice@zodia.io](mailto:customerservice@zodia.io)
3. Generate ECC key pairs for at least one maker and one checker user
4. Send user emails, public keys, and roles to [customerservice@zodia.io](mailto:customerservice@zodia.io)
