One of the most difficult things to do in the Farcaster ecosystem is allowing users to make writes to the network from your app. Thankfully Pinata makes this easy with Farcaster Auth.

Primer to Farcaster Signers

In the Farcaster ecosystem there are a few ways you can make writes to the network, and they all revolve around the ED25519 signer. All Farcaster accounts are sets of public and private keypairs, or simply EOA wallets. Instead of prompting users to paste in their mnemonic seed phrase into an app, the more popular approach is to create a new keypair that the user can approve to be used to write on their behalf. This is much safer as the user can revoke these keypairs at any time.

The only problem is managing the signer key. Some developers store the key in a user’s local storage, which could get deleted if they clear their browser history or do some kind of cache clear. Others might manage the signers themselves in their database, but there are liability risks here. Farcaster Auth takes both of those problems away with an API and SDK that can mange the signers and create a simpler flow for users.

How it works

A high level view of Farcaster Auth looks something like this

  • Create a new signer with POST /signers
  • Sign the public key returned by the API with your FARCASTER_DEVELOPER_MNEMONIC
  • Send a request with the signature payload to register the signer with warpcast
  • API returns a polling token and a deep_link_url. Use the token to check the status of the approval, then give the end user the deep_link_url in the form of a QR code or link that will open Warpcast
  • Continue to poll the token, and once the signer is approved the process will be complete. The signer_id at this point can be used to make post requests.

Using Signers

Once you have an approved signer you can fetch the signerId from GET /signers?fid=USER_FID and filter by FID if you need to. Once you have the signerId you can use it in other endpoints like sending casts.

Getting Started

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.

You will also need the mnemoic seed phrase for your app account, as this will sign the key and let the user know what app they are signing into when approving it in Warpcast. 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.

Since this is non-sponsored managed signers the user will need to pay warps at least once. After that the key is linked to your Pinata account and remains active until the usere revokes it.

// 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()

//"data": {
//  "signer_id": "ee5605a0-9833-4d30-8c22-7ac315061ee7",
//  "token": "0x2ef159dc1e9f6a2sb0d2353f",
//  "deep_link_url": "farcaster://signed-key-request?token=0x2efb59dv1e9f642fb07f353f",
//  "status": "pending_approval"

Poll Signer for Approval Status

At this point we should immediately start polling the signer, waiting for the user to approve the key. You might have noticed in the response to /register_signer_with_warpcast is the deep_link_url. This is a special URL that when opened on a mobile device with Warpcast installed, will prompt the user to approve the signer/login. This can be given to the user in the form of a QR code or just a link that the user opens.

Once they approve the signer 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 request to register_signer_with_warpcast.

// Previous code...

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

//"signedKeyRequest": {
//  "token": "0x2ef159dc1e9f6a2sb0d2353f",
//  "deeplinkUrl": "farcaster://signed-key-request?token=0x2efb59dv1e9f642fb07f353f",
//  "key": "0xe57c09c48b0ba27d95676309416134ad1409d3cg27ccb354b73eb819aa117f2f",
//  "requestFid": 6023,
//  "state": "completed",
//  "isSponsored": false,
//  "userFid": 6023

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;

//"signers": [
//    {
//        "id": 25,
//        "signer_uuid": "bd9cea0c-ab31-4c71-a46f-a169ba626192",
//        "fid": 6023,
//        "public_key": "e57c09c48b0ba27d95676309416134ad1409d3cg27ccb354b73eb819aa117f2f",
//        "signer_approved": true,
//        "revoked": false
//    }
//"next_page_token": "eyJvZmZzZXQiOiIyNSJ9"

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"