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

# Authentication

> Bearer keys: format, scope, rotation, revocation — and the two things never to put in a request.

## Getting an API key

Needlepath is in early access. Keys are issued per organisation, not
self-serve: request access through [nextmoca.com](https://nextmoca.com) and a
key arrives with your onboarding. Everything below applies to the key you
receive.

Every request carries one header:

```http theme={null}
Authorization: Bearer np_live_…
```

There is no alternative. No query parameter, no cookie, no `x-api-key`. The
legacy `x-api-key` header was retired at the Bearer cutover and now fails
authorization outright.

## Key format

```
np_live_<22 characters of entropy><6 character checksum>
```

| Property                      | Why it is that way                                                                                                                                                                                                                                                         |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `np_` prefix                  | A leaked key is **grep-able**, and the shape is registered with GitHub secret scanning — the highest-leverage anti-leak control there is.                                                                                                                                  |
| 6-character checksum          | Lets a scanner confirm a candidate string is a real key *shape* without calling us. It is a **typo and scanner aid, not a security control**: it is computed by a published algorithm from public information, so a well-formed string proves nothing about authorisation. |
| \~131 bits of entropy         | Drawn from a CSPRNG. There is no dictionary to attack.                                                                                                                                                                                                                     |
| Stored as an unsalted SHA-256 | A high-entropy machine credential does not need a salt or a slow KDF the way a password does, and a per-key salt would force a scan on the authorization path.                                                                                                             |

`np_test_` is reserved and is not minted today.

## Scope: keys belong to the organisation, not to you

An API key is **org-scoped**. This has two consequences that surprise people at
exactly the wrong moment:

<AccordionGroup>
  <Accordion title="When someone leaves, their keys keep working">
    That is correct behaviour, not an oversight. Revoking a person must not strand
    their company's production traffic. What you should do at offboarding is review
    the keys that person created and rotate the ones nobody is now accountable for.

    Keep your own record of which key belongs to which service and who provisioned
    it — a *"keys created by X"* view arrives with the dashboard, and until then
    that mapping lives with you.
  </Accordion>

  <Accordion title="Everyone on the org shares one allowance">
    Rate limits, quotas and the per-request ceiling apply to the organisation.
    Mint separate keys per service anyway: it is the only way to attribute usage,
    and it means revoking one integration does not take down the rest.
  </Accordion>
</AccordionGroup>

## The secret is shown exactly once

At mint time you see the full key. After that we hold only its hash and a
display prefix, so nobody — including us — can recover it. If you lose it,
rotate.

## Rotation is create-then-revoke, never in place

<Note>
  **Key operations go through us today.** There is no self-serve console for
  minting, rotating or revoking yet — that arrives with the account dashboard. The
  sequence below is what we will do on your behalf, and it is what the self-serve
  flow will do when it lands, so it is worth understanding either way.
</Note>

<Steps>
  <Step title="Create a second key">
    Both keys are now live. Your per-org key cap has to have room for the extra —
    see [Limits](/limits#keys-per-organisation).
  </Step>

  <Step title="Deploy the new key">
    Roll it out everywhere the old one was used. Take as long as you need; there
    is no window where neither key works.
  </Step>

  <Step title="Confirm the old key is idle">
    Wait until nothing is using it. Your own deploys are the primary evidence; ask
    us and we can confirm from telemetry, which is per-key. A per-key
    last-used timestamp you can read yourself arrives with the dashboard.
  </Step>

  <Step title="Revoke the old key">
    Revocation is not instantaneous on the request path — see below.
  </Step>
</Steps>

Rotating by deleting first is the one sequence that causes an outage, which is
why the flow is named this way rather than called "rotate".

## Revocation takes up to 60 seconds to bite

Authorization decisions are **cached for 60 seconds**. A revoked key can
therefore still be accepted for up to a minute after you revoke it.

This is a deliberate trade: the same cache is what keeps authorization off the
request path, and a 60-second TTL is already five times tighter than the
platform default.

<Warning>
  **If a key is actively being abused, 60 seconds is too long.** Contact support
  and say so. There is an immediate incident path that stops the key at the
  gateway rather than waiting for a cache to expire — it is not something you can
  trigger from an API call, and it is the correct escalation for a live leak.
</Warning>

The same TTL applies to *every* claim carried on your key — tier, limits,
status. A plan change or a suspension is subject to the same window.

## What never to put in a request

Two fields, for two different reasons.

<Warning>
  **Never put a credential — or any secret — in `request_id`.**

  `request_id` is your correlation key and we log it, deliberately, so that "what
  happened to my request" is an answerable question. It is bounded to **128
  characters from `[A-Za-z0-9._:-]`** and anything outside that is truncated and
  de-fanged before it is written (the response then carries
  `format_metrics.request_id_sanitized: true`), but **bounding is not erasure**: a
  legal string under the cap reaches our logs intact. Put an identifier there, and
  nothing else.
</Warning>

<Note>
  **Record content belongs in `records[].text`, not in metadata.**

  `keywords`, `tags` and `attributes` are structurally bounded and are not
  metered. They are bounded by the published size cap; values beyond it are
  outside the contract and may be rejected.

  They are read, as indexing hints, so short well-chosen values help. They are not
  content: nothing parked there can be selected, excerpted or returned to you.
</Note>

## Errors you will see

| Status | Means                                                                                                                                                            | Do                                                                       |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `401`  | **No `Authorization` header at all.** Rejected before the service is reached.                                                                                    | Fix the client.                                                          |
| `403`  | A header arrived and was refused — including one that is present but not `Bearer`-shaped, and an unknown, revoked or malformed key, or a suspended organisation. | Fix the credential. **Do not retry** — no cause of a `403` is transient. |

The `403` body deliberately does not say *which* of those it was. An error that
distinguishes "no such key" from "suspended organisation" is an oracle for
anyone probing keys.

<Card title="Full error reference" icon="triangle-exclamation" href="/errors">
  Every status this API returns, what causes it, and whether to retry.
</Card>
