[GH-ISSUE #3403] apiKey.create return 400 BAD REQUEST api/auth/api-key/create #9591

Closed
opened 2026-04-13 05:07:38 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @jpainam on GitHub (Jul 16, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3403

Hi.
This is my way of creating an apikeys

const createApiKey = async () => {
    setIsLoading(true);
    const { data: apiKey, error } = await authClient.apiKey.create({
      name: "My API Key",
      rateLimitEnabled: false,
      //expiresIn: 60 * 60 * 24 * 7, // Omit for never expiring key
      prefix: "disc",
    });
    if (!error) {
      openModal({
        title: t("API key"),
        view: <ShowApiKey apiKey={apiKey.key} />,
      });
      setIsLoading(false);
      return;
    }
    setIsLoading(false);
  };

and my config is

export function initAuth() {
  const config = {
    database: prismaAdapter(db, {
      provider: "postgresql",
    }),
    secret: "secret",
    emailAndPassword: {
      enabled: true,
      
    },
   
    plugins: [
      admin(),
      username(),
      apiKey({
        enableMetadata: true,
      }),
      oAuthProxy(),
      expo(),
      nextCookies(),
    ],
  } satisfies BetterAuthOptions;

  return betterAuth(config);
}

But when creating the api key, i get this error

better-auth.Dw8i6Dcb.mjs:55  POST http://localhost:3000/api/auth/api-key/create 400 (BAD_REQUEST)

What can be the issue
the connected user has a role admin

Originally created by @jpainam on GitHub (Jul 16, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3403 Hi. This is my way of creating an apikeys ```ts const createApiKey = async () => { setIsLoading(true); const { data: apiKey, error } = await authClient.apiKey.create({ name: "My API Key", rateLimitEnabled: false, //expiresIn: 60 * 60 * 24 * 7, // Omit for never expiring key prefix: "disc", }); if (!error) { openModal({ title: t("API key"), view: <ShowApiKey apiKey={apiKey.key} />, }); setIsLoading(false); return; } setIsLoading(false); }; ``` and my config is ```tsx export function initAuth() { const config = { database: prismaAdapter(db, { provider: "postgresql", }), secret: "secret", emailAndPassword: { enabled: true, }, plugins: [ admin(), username(), apiKey({ enableMetadata: true, }), oAuthProxy(), expo(), nextCookies(), ], } satisfies BetterAuthOptions; return betterAuth(config); } ``` But when creating the api key, i get this error ```tsx better-auth.Dw8i6Dcb.mjs:55 POST http://localhost:3000/api/auth/api-key/create 400 (BAD_REQUEST) ``` What can be the issue the connected user has a role `admin`
GiteaMirror added the locked label 2026-04-13 05:07:38 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jul 16, 2025):

A 400 BAD REQUEST from apiKey.create usually means the server rejected the request due to invalid or missing input. The most common causes are: the name or prefix fields are missing or their lengths are outside the allowed range (1–32 characters), or the metadata field is not an object if provided. The expiresIn value, if used, must be between 1 and 365 days. Double-check that all fields in your payload match these requirements and that types are correct. If everything looks correct, check your server logs for a more specific error message, as the server should return a detailed reason for the 400 error (like INVALID_NAME_LENGTH, INVALID_PREFIX_LENGTH, or INVALID_METADATA_TYPE) reference.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

<!-- gh-comment-id:3077260558 --> @dosubot[bot] commented on GitHub (Jul 16, 2025): <!-- Answer --> A 400 BAD REQUEST from apiKey.create usually means the server rejected the request due to invalid or missing input. The most common causes are: the name or prefix fields are missing or their lengths are outside the allowed range (1–32 characters), or the metadata field is not an object if provided. The expiresIn value, if used, must be between 1 and 365 days. Double-check that all fields in your payload match these requirements and that types are correct. If everything looks correct, check your server logs for a more specific error message, as the server should return a detailed reason for the 400 error (like INVALID_NAME_LENGTH, INVALID_PREFIX_LENGTH, or INVALID_METADATA_TYPE) [reference](https://github.com/better-auth/better-auth/blob/7364c169f6de0eec4f095112047e23480b240078/packages/better-auth/src/plugins/api-key/index.ts). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/5301d6ce-0d64-4214-9b7c-d6ade1bc5e70?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/5301d6ce-0d64-4214-9b7c-d6ade1bc5e70?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/5301d6ce-0d64-4214-9b7c-d6ade1bc5e70?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/5301d6ce-0d64-4214-9b7c-d6ade1bc5e70?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/5301d6ce-0d64-4214-9b7c-d6ade1bc5e70?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/5301d6ce-0d64-4214-9b7c-d6ade1bc5e70?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/5301d6ce-0d64-4214-9b7c-d6ade1bc5e70?feedback_type=other)</sup>&nbsp;&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3403)
Author
Owner

@jpainam commented on GitHub (Jul 16, 2025):

Using this on the server,

const result = await auth.api.createApiKey({
    body: {
      name: "My API Key",
      rateLimitEnabled: false,
      //expiresIn: 60 * 60 * 24 * 7, // Omit for never expiring key
      prefix: "disc",
    },
    headers: await headers(),
  });
hook.js:608 APIError: The property you're trying to set can only be set from the server auth instance only.
    at resolveErrorDev (react-server-dom-turelopment.js:1858:46)
    at processFullStringRow (react-server-dom-turelopment.js:2238:17)
    at processFullBinaryRow (react-server-dom-turvelopment.js:2226:7)
    at progress (react-server-dom-turelopment.js:2472:17)

<!-- gh-comment-id:3077366250 --> @jpainam commented on GitHub (Jul 16, 2025): Using this on the server, ```tsx const result = await auth.api.createApiKey({ body: { name: "My API Key", rateLimitEnabled: false, //expiresIn: 60 * 60 * 24 * 7, // Omit for never expiring key prefix: "disc", }, headers: await headers(), }); ``` ```tsx hook.js:608 APIError: The property you're trying to set can only be set from the server auth instance only. at resolveErrorDev (react-server-dom-tur…elopment.js:1858:46) at processFullStringRow (react-server-dom-tur…elopment.js:2238:17) at processFullBinaryRow (react-server-dom-tur…velopment.js:2226:7) at progress (react-server-dom-tur…elopment.js:2472:17) ```
Author
Owner

@Kinfe123 commented on GitHub (Jul 16, 2025):

for the first one, you are trying to access set a server only property which is ratelimitEnabled from the client call. and for second you can pass the userId for now and it should work.

<!-- gh-comment-id:3081307462 --> @Kinfe123 commented on GitHub (Jul 16, 2025): for the first one, you are trying to access set a server only property which is ratelimitEnabled from the client call. and for second you can pass the userId for now and it should work.
Author
Owner

@jpainam commented on GitHub (Jul 17, 2025):

I passed user id and it worked. Since i passed the headers(). I was expecting it to pull the user information from the headers. If user id is required. I think it’s just better to make it a required field

<!-- gh-comment-id:3081968594 --> @jpainam commented on GitHub (Jul 17, 2025): I passed user id and it worked. Since i passed the headers(). I was expecting it to pull the user information from the headers. If user id is required. I think it’s just better to make it a required field
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9591