Skip to content

Authentication

Every API call carries the key in the Authorization header, in Bearer format:

Authorization: Bearer fh_live_your_key

That is the only accepted way. A key in a query parameter is not read, because a URL with a key inside leaks into proxy logs, into browser history and into the Referer header.

fh_live_ + 43 body characters + 6 check characters

That is 57 characters in total. Example of what the panel shows in the key list: fh_live_7Qx4Kd, the first 14 characters. That is the piece GET /public/v1/me returns in key.prefix, and it is how you tell which key is in use without holding the whole key.

The last 6 characters are a checksum computed from the rest. The API uses the checksum to refuse a mistyped key without even going to the database.

The checksum also makes the format easy to search for: a rule matching fh_live_ followed by 43 body characters and 6 check characters almost never fires on anything else. You are the one who sets that rule up, in the secret scanning of your git provider or in your own pipeline. No provider recognises this format on its own.

The fh_test_ prefix is reserved and never issued. There is no test key because there is no test environment.

Keys issued in the first version of the API, without the 6 check characters, are still valid.

The Accept-Language header picks the language of the error message field. pt-BR (the default), en and es are accepted, and the q weight is honoured. The code field is always the same, in English: write your code against it.

Every authentication refusal answers the same thing

Section titled “Every authentication refusal answers the same thing”

When the API does not accept the key, the answer is always this one, with HTTP 401:

{
"error": {
"type": "authentication_error",
"code": "api_key_invalid",
"message": "Invalid, revoked or expired API key.",
"request_id": "0a8c1f2e-3b4d-4c5f-9a6b-7c8d9e0f1a2b",
"doc_url": "https://docs.fatureihoje.com/en/errors#api_key_invalid"
}
}

It is the same for all of these cases:

  • Authorization header missing or not in Bearer format;
  • key with an invalid format, or with the wrong checksum;
  • key that does not exist;
  • revoked key;
  • expired key;
  • key whose member was suspended, removed or had the access disabled;
  • inactive company;
  • source IP outside the key’s IP allowlist.

This is on purpose. A different answer per reason would tell someone testing random keys when they hit a real key and only missed the IP. The cost lands on you, while debugging, and the rest of this page exists to make up for it.

The response does not say, but the panel does. Check in this order:

  1. The header. Authorization: Bearer <key>, a single space, no quotes around the key.
  2. The key. Copied whole, with no space or line break at the end. Compare the first 14 characters with the prefix the panel shows.
  3. The key state. The panel marks each key as Active, Rotating, Expired or Revoked.
  4. The IP allowlist. If the key has a list, your server’s outbound IP has to be on it.
  5. The address. Only api.fatureihoje.com answers /public/v1. The panel host returns 404.
  6. The usage log. In the key menu, under View usage. It shows the calls of the last 30 days with date, method, route, status, duration, IP and request_id. A refused call enters the log when the API recognises which key it is: revoked key, expired key, rotated key, suspended member, deactivated user or IP outside the list. That is how you find both the wrong IP and a key of yours being used from a place where it should not be.

The two in the middle are the least obvious, which is why they deserve the warning: suspending a team member or deactivating their user takes down the integration that their key acts as, with no other sign. If an integration stopped on the same day somebody left the team, it is almost always that.

Two refusals leave no line at all: a token that belongs to no key, because there is no company to attribute the attempt to, and an inactive company, because then the whole panel is blocked and the problem is not the key. If nothing shows up under View usage, start by checking those two.

Every response carries the Request-Id header, and the error body repeats the value in request_id. It is how support finds the call.

Terminal window
curl -i https://api.fatureihoje.com/public/v1/me \
-H "Authorization: Bearer $FH_API_KEY"

-i prints the response headers along with the body.

The API counts authentication failures per source IP. Past 60 failures per minute, the next calls from that IP get a 429 with the Retry-After header, even with the right key. Whoever uses the right key never spends that budget.

When you get a 401, stop and fix the configuration. Repeating the same wrong call only delays the fix.

Each key accepts a list of up to 20 addresses or ranges. An empty list accepts any IP.

Accepted formats, IPv4 and IPv6:

198.51.100.7
198.51.100.0/24
2001:db8::1
2001:db8::/32

A bare address means that exact machine (/32 on IPv4, /128 on IPv6).

The mask is applied on save. If you type 203.0.113.5/24, the entry is stored as 203.0.113.0/24, because that is what the range means. The panel shows the normalised value: what is on screen is exactly what applies.

An invalid entry is neither accepted nor silently dropped: the panel refuses the form. Dropping it silently could empty the list, and an empty list allows any IP.

A call from an IP outside the list gets the same 401 of an invalid key, and the attempt shows up under View usage with the IP that arrived.

At creation you choose between never expires, 30 days, 90 days or 1 year. After that date the key answers 401 like any invalid key. A key with an end date limits the damage if it ever leaks.

Rotating generates a new key with the same name, the same permissions, the same member, the same IP list and the same expiration date, and puts the old one on a timer.

In the panel: key menu, Rotate. You choose how long the old one keeps working: stop now, 1 hour or 24 hours. During that window both keys answer, and that is what lets you swap the secret in your systems without breaking the integration.

Step by step:

  1. Rotate, choosing the overlap window.
  2. Copy the new key, which is also shown once.
  3. Swap the secret in your systems and run a GET /public/v1/me with the new key.
  4. Revoke the old one as soon as you confirm. You do not need to wait for the window to end.

While the old one is alive, the panel marks it as Rotating.

Four details that usually catch people:

  • The new key inherits the expiration date of the old one. Rotating a key that expires next week gives you a key that also expires next week. Rotation swaps the secret, it does not renew the term: to gain term, create a new key.
  • During the overlap both keys count against the plan’s active key quota. Rotation itself is not blocked by that, but creating one more key is, until the old one is gone.
  • An old key that was already rotated cannot be rotated again. Whoever rotates twice rotates the new key.
  • On the old key, an expiration date earlier than the end of the overlap wins over the overlap: it dies on that date, not at the end of the window you chose.

Revoke in the panel ends the key right away. The next call with it gets a 401. There is no undo and no grace period: if the key leaked, this is what you do first.

The OWNER and ADMIN of the company get an email when a key is created, rotated or revoked.

  • Permissions: what the key can do once authenticated.
  • Reference: the published routes, field by field.