To get started with Farcaster Auth you will need to get a Pinata API Key on a paid Pinata account, which you can view pricing here.

You will also need an App FID. Generally you would make a fresh Farcaster account for your app, like @photocast which as an FID of 327481.

If you are using non-sponsored signers you will also need the mnemoic seed phrase for your app account. This is usually provided to you when creating the account for backup purposes. If you had an env file it might look something kike this:

FARCASTER_DEVELOPER_MNEMONIC=pinata confetti ipfs farcaster cloud developer send it

Signer Flow


Setup ENV variables

As stated previously you will need to make sure you have your App FID and App mnemomic phrase somewhere secure where you can access them in the following steps. Doing in Javascript might look something like this.

FARCASTER_DEVELOPER_MNEMONIC=pinata confetti ipfs farcaster cloud developer send

Create Signer with `POST /signers`

After declaring some types and importing viem/accounts we can declare the constants and start the process of creating a signer.

import { mnemonicToAccount } from "viem/accounts";

  name: "Farcaster SignedKeyRequestValidator",
  version: "1",
  chainId: 10,
  verifyingContract: "0x00000000fc700472606ed4fa22623acf62c60553",
} as const;

  { name: "requestFid", type: "uint256" },
  { name: "key", type: "bytes" },
  { name: "deadline", type: "uint256" },
] as const;

const appFid = process.env.FARCASTER_DEVELOPER_FID;
const account = mnemonicToAccount(

async function createSigner() {
  const res = await fetch("", {
    method: "POST",
    headers: {
      'Content-Type': "application/json",
      "Authorization": `Bearer YOUR_PINATA_JWT`
    body: JSON.stringify({
      app_fid: parseInt(appFid, 10)

  const signerInfo: any = await res.json();
  const { data }: { data: { signer_uuid: string, public_key: string, signer_approved: string } } = signerInfo;

Sign Key Provided by Farcaster Auth

Now that the key is created we will need to sign the key with our developer Farcaster account

/// previous code
const deadline = Math.floor( / 1000) + 86400; // signature is valid for 1 day
const requestFid = parseInt(appFid);
const signature = await account.signTypedData({
  types: {
    SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE,
  primaryType: "SignedKeyRequest",
  message: {
    requestFid: BigInt(appFid),
    key: `0x${data.public_key}`,
    deadline: BigInt(deadline),

Register Signer with Warpcast

Once the key is signed we can now make a request to Warpcast to register the key. This is where we will get a Farcaster deep link (farcaster://) that we can have the user either click on or scan with a QR code that will open the Warpcast app for them to approve the signer.

At this point you can make the decision to use sponsored or non-sponsored signers.


With sponsored signers the end user will not have the pay warps when they approve the sign in, however you may be limited to the number of sponsored signers you may have based on your Pinata plan. Sponsored signers is enabled with the query parameter ?sponsored=true.


With non-sponsored signers the end user will have to pay warps to approve the signer, however there is no limit to how many non-sponsored signers you can have on your Pinata account.

// previous code

const registerResponse = await fetch(``, {
  method: "POST",
  headers: {
    'Content-Type': "application/json",
    "Authorization": `Bearer YOUR_PINATA_JWT`
  body: JSON.stringify({
    signer_id: data.signer_uuid,
    signature: signature,
    deadline: deadline,
    app_fid: requestFid,
    app_address: account.address

const warpcastPayload = await registerResponse.json()

Poll Signer for Approval Status

At this point we should immediately start polling the signer, waiting for the user to approve the key. Once they do we’ll get a response back from Warpcast that will register the key with that user’s account and make it accessible via Farcaster Auth. To do that we’ll use poll_warpcast_signer with a query parameter of token that’s provided by the previous warpcastPayload.

// Previous code...

const pollSigner = await fetch(`${}`, {
  method: 'POST',
  headers: {
    'Content-Type': "application/json",
    "Authorization": `Bearer YOUR_PINATA_JWT`
const pollSignerRes = awwait pollSigner.json()

If successful you should get a response with a completed state, at which point you can now use the signer_id returned in the Register Signer step.


Get Signers

After registering you can get the signer_id at any point using GET /signers endpoint with a query parameter of fid if you need to filter the results.

async function getSigners(fid?: number){
  try {
    const req = await fetch(`${fid}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer YOUR_PINATA_JWT`,
    const res = await req.json();
    const resultData =
    return resultData
  } catch (error) {
    throw error;

With the signer_id you can then use it in other API calls like POST /casts to send a cast.

curl --location '' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_PINATA_JWT' \
--data '{
  "castAddBody": {
    "text": "Hello World!",
    "parentUrl": ""
  "signerId": "SIGNER_ID"