Documentation ▸

Recipients

Totals follow recipients, not spellings.

Some rules add up: the limit on gifts is per recipient per fiscal year, across cards, invoices and reimbursements. So totals follow recipients, not spellings. Each recipient resolves to one identity in your account, and the yearly total is that identity's.

How a recipient becomes an identity

A check sends the recipient as a descriptor — a name, an organisation, an email address, a customer or supplier number — or as an id you already hold. The descriptor is replaced by a token before anything is stored, and looked up against the recipients already on file:

  • the same customer or supplier number is the same recipient;
  • the same spelling is the same recipient, unless that recipient carries another customer or supplier number than the one you sent: a number is an identity, so the same name under another number is somebody else. A number you send with a spelling already on file is kept with it, and one on file is never replaced. A spelling two of your customers share, sent with no number, is not guessed onto either: it is a recipient of its own until someone links it;
  • the same email address is the same recipient, as the same spelling is, when you send it alone, or with a name where the recipient on file under it has none yet. Upper and lower case, and spaces around it, make no second address. A recipient under another customer or supplier number is passed over, as for a spelling, and so is one on file under a different name: two names under one address may be a household or a team sharing it, so a person is asked (below). An address two of your recipients share, sent alone, is not guessed onto either;
  • a recipient that your confirmed matching rules call the same is linked to it;
  • one that only might be the same — the same address under another name — is never merged silently. It becomes a probable match for a person to answer. Once somebody has answered that two names at an address are two people, the address is a shared one — a team's inbox, a household's — and a name sent from it is asked about no more.

A name is a recipient only as it is written, its capitals and spacing aside: "A. Beispiel" beside "Anna Beispiel" is another recipient, and nobody is asked — for all anyone knows, A. Beispiel is Albert. An address is compared as written too: anna.beispiel@ and anna_beispiel@ are two addresses. So where a limit is per customer, send the customer's number: it is the one identity a spelling cannot split.

A recipient already on file that arrives under a name or an address it has not had before — a customer your support tool first knew by email address, whose name comes later — is compared with the others the same way, and a question that raises is put to a person. The answers given to it before are issued again where the question changes them (recheck_reason: probable_match).

Your matching rules are a rule set of your own, confirmed on their confirmation page like any other. Confirming them compares the recipients already on file, and every pair they call probable becomes a review item. The answers already given to the newer record of such a pair are issued again (recheck_reason: probable_match), waiting on the question wherever it changes them.

Probable matches

GET /v1/recipients/matches lists the pairs waiting for an answer, each with both records' ids. Your own system already knows who they are: it sent the names, and got a recipient_id back on every check. Answer each one:

curl https://api.gethimmel.com/v1/recipients/matches/rvw_01J9... \
  -H "Authorization: Bearer $HIMMEL_KEY" -H "Content-Type: application/json" \
  -d '{ "decision": "same", "decided_by": "usr_01J9Q4TZ8M..." }'

same links the two records into one identity; different keeps them apart for good. Either way, every answer whose total depends on the pair is issued again — in a job, whose id comes back as recheck_job — and each changed answer raises a check.rechecked alert.

While a probable match is open, a check whose answer depends on it — because the total would cross the limit if the two were one person — answers undetermined_missing_facts, naming recipient_identity and the review item. Where the answer is the same either way, the check is determined regardless. That holds for every booking of the record a question is about — a in the listing — whenever it arrived: a later booking under a spelling already on file waits on the question too, and so does one sent before the question was raised.

Once the pair is answered, the booking that asked is answered like any other: counted with the other record after same, on its own after different. Its answer is issued again with the rest, its recipient reads resolved, and where the question was all it lacked it is now determined — with a check.rechecked alert, since its state moved.

POST /v1/recipients/{id}/split undoes a record's links to others. The links stay on record, marked undone with who undid them and when — what was believed, and when it stopped being believed, is part of the evidence — and the answers they moved are issued again.

Names

GET /v1/recipients?ids= asks for given records by id, up to 200 separated by commas — the ones a page of the check log names, say — where the listing alone gives the newest.

To someone signed in to the console, whatever their role, each record also gives its numbers: the numbers your systems sent it with, as sent — customer_number, supplier_number, vendor_number, creditor_number, each kind once — and {} where none was. The console shows a customer sent by number alone — as an agent sends one — by its number. A key is given neither names nor numbers: your software holds both already, and a key that leaks lists no customer by them.

A key is never given a name. GET /v1/recipients and GET /v1/recipients/matches return recipient ids to a key, whatever its scopes, and a search by name (GET /v1/recipients?q=) is refused with 403 names-withheld: a search that answers "whose name contains this?" would spell names out a letter at a time.

Names are shown in the console, to the people who answer whether two records are the same person: admins and ops, signed in. For them the same two endpoints add each record's label, and q searches by it. A record's label is the first name that arrived with it: a customer first sent by number or by address alone is shown by the first name sent with them later, and keeps it. Every other answer — checks, the check log, alerts — carries recipient ids only, for everyone.

How long names are kept

A customer's names, organisations and addresses are kept through the end of the fiscal year after the one they were last met in — by any check, whichever way it found them, by number too — and then deleted. Where a record's name was deleted, the listings give label: null with name_expired: true; a null label alone means only a number was recorded. The record and its numbers stay, and so does what finds it: send the same spelling, number or address again and it is the same customer, and the name is kept again from then. What no longer reaches it is the matching rules, which compare names — a customer nobody has met for two fiscal years has no total that needs them.

Endpoints

GET/v1/recipientsScope checks:read

Recipient identities

Each recipient record, newest first, with the identity it counts as once confirmed links are followed. A person signed in to the console as an admin or ops is also shown each record's name, and can search by it with q. A key is shown ids and never a name, so q is refused for it (names-withheld). ids asks for given records - the ones a list of checks names, say - where the listing alone is the newest.

Needs a key with the scope checks:read. What it reads is your whole account's, so a key made for an agent, which reads its own agent's alone, gets 403 insufficient-scope.

Parameters

  • qquery · string or null
  • idsquery · string or null

    Only these records, by id, separated by commas: up to 200.

  • limitquery · integer

Responses

200 Successful response
  • recipientslist of RecipientRecordrequired
    8 fields
    • recipient_idstringrequired
    • kindstringrequired
    • created_fromstringrequired
    • created_atstringrequired
    • canonical_idstringrequired

      The identity this record counts as, after confirmed links.

    • labelstring or null

      The recipient's name as recorded, or its organisation or email address where no name was, read through the pseudonymiser. Shown only to a person signed in to the console as an admin or ops, and left out for a key, whatever its scopes. Null when only a number was recorded, or when the name is no longer kept (name_expired).

    • name_expiredboolean or null

      True where a name was recorded and is no longer kept: a customer's names are kept through the end of the fiscal year after the one they were last met in. Their record and numbers stay, and the same spelling sent again finds them and brings the name back. Left out otherwise.

    • numbersmap of string or null

      The numbers your systems sent the record with, as sent - customer_number, supplier_number, vendor_number, creditor_number, each kind once - and {} where none was: what the console shows a customer by where no name was sent. Shown to a person signed in to the console, whatever their role, and left out for a key.

  • 401No API key was sent, or it is not in use (unauthorized).
  • 403The caller may not do this, and type says why: the key or role lacks the scope, the rule set is not enabled for this account, the person signed in belongs to no account, names were asked for by a caller that is not shown them, or a request acting in test tried to change the account's settings (live-only).
  • 422The request, or a fact in it, is not in the documented shape - a query parameter the operation does not take included, which is named (unknown_parameter) rather than ignored - or its Himmel-Env does not fit the caller. Missing facts are not errors.
  • 429More requests in a minute than the key's or the person's limit (rate-limited) - or, opening an account, more accounts in a day than the deployment opens, with Retry-After saying when to try again.
  • 500Something failed on our side. X-Request-Id identifies it in our logs.
GET/v1/recipients/matchesScope checks:read

Probable matches waiting for somebody to answer

Two recipient records a confirmed matching rule thinks may be one person. Totals follow recipients, not spellings, and a probable match is never merged without a person saying so: answer each with POST /v1/recipients/matches/{id}. Both records' names are shown to a person signed in to the console as an admin or ops; a key is shown their ids.

Needs a key with the scope checks:read. What it reads is your whole account's, so a key made for an agent, which reads its own agent's alone, gets 403 insufficient-scope.

Parameters

  • statusquery · string
  • limitquery · integer

Responses

200 Successful response
  • matcheslist of ProbableMatchrequired
    6 fields
    • review_itemstringrequired
    • status"open" | "same" | "different"required
    • rulestringrequired

      The confirmed matching rule that proposed it.

    • created_atstringrequired
    • aMatchSiderequired
      3 fields
      • recipient_idstringrequired
      • labelstring or null

        The recipient's name as recorded, or its organisation or email address where no name was, read through the pseudonymiser. Shown only to a person signed in to the console as an admin or ops, and left out for a key, whatever its scopes. Null when only a number was recorded, or when the name is no longer kept (name_expired).

      • name_expiredboolean or null

        True where a name was recorded and is no longer kept: a customer's names are kept through the end of the fiscal year after the one they were last met in. Their record and numbers stay, and the same spelling sent again finds them and brings the name back. Left out otherwise.

    • bMatchSiderequired
      3 fields
      • recipient_idstringrequired
      • labelstring or null

        The recipient's name as recorded, or its organisation or email address where no name was, read through the pseudonymiser. Shown only to a person signed in to the console as an admin or ops, and left out for a key, whatever its scopes. Null when only a number was recorded, or when the name is no longer kept (name_expired).

      • name_expiredboolean or null

        True where a name was recorded and is no longer kept: a customer's names are kept through the end of the fiscal year after the one they were last met in. Their record and numbers stay, and the same spelling sent again finds them and brings the name back. Left out otherwise.

  • 401No API key was sent, or it is not in use (unauthorized).
  • 403The caller may not do this, and type says why: the key or role lacks the scope, the rule set is not enabled for this account, the person signed in belongs to no account, names were asked for by a caller that is not shown them, or a request acting in test tried to change the account's settings (live-only).
  • 422The request, or a fact in it, is not in the documented shape - a query parameter the operation does not take included, which is named (unknown_parameter) rather than ignored - or its Himmel-Env does not fit the caller. Missing facts are not errors.
  • 429More requests in a minute than the key's or the person's limit (rate-limited) - or, opening an account, more accounts in a day than the deployment opens, with Retry-After saying when to try again.
  • 500Something failed on our side. X-Request-Id identifies it in our logs.
POST/v1/recipients/matches/{match_id}Scope recipients:write

Same, or different. Never automatic.

same links the two records into one identity and different keeps them apart for good. Either way the answers whose totals depend on it are issued again, in a job - the booking that raised the question among them, answered from then on like any other: with the other record after same, on its own after different.

Parameters

  • match_idpath · stringrequired
  • Idempotency-Keyheader · string or null

    Makes a retry safe: the same key with the same request within 24 hours gets the first answer again, and nothing is done twice. The same key with a different request is 409 idempotency-conflict. Keys are per environment and per path.

Request body · application/json

  • decision"same" | "different"required
  • decided_bystring or null

    Who did it, when your key acts for a person: their Himmel id, which the console's Developers page shows them, and Settings' People an admin. They must be in your account, with a role that could do this themselves, and the record keeps it as your key's claim. A key made for an agent names nobody but itself: leave it out. Signed in to the console, leave it out: it is you.

Responses

200 Successful response
  • review_itemstringrequired
  • decision"same" | "different"required
  • recipientslist of stringrequired
  • recheck_jobstring or null
  • 401No API key was sent, or it is not in use (unauthorized).
  • 403The caller may not do this, and type says why: the key or role lacks the scope, the rule set is not enabled for this account, the person signed in belongs to no account, names were asked for by a caller that is not shown them, or a request acting in test tried to change the account's settings (live-only).
  • 404Nothing with that id or name exists for this account.
  • 409The request conflicts with what is stored: see detail.
  • 413The request's body is larger than the operation takes, refused before any of it is read (body-too-large): a policy's files at most 5 MB, any other body 1 MB - and an uploaded file at most 20 MB (file-too-large).
  • 422The request, or a fact in it, is not in the documented shape - a query parameter the operation does not take included, which is named (unknown_parameter) rather than ignored - or its Himmel-Env does not fit the caller. Missing facts are not errors.
  • 429More requests in a minute than the key's or the person's limit (rate-limited) - or, opening an account, more accounts in a day than the deployment opens, with Retry-After saying when to try again.
  • 500Something failed on our side. X-Request-Id identifies it in our logs.
POST/v1/recipients/{recipient_id}/splitScope recipients:write

Undo a link, and re-check what it moved

Undoes this record's links to others. The links stay on record, marked undone, and the answers they moved are issued again.

Parameters

  • recipient_idpath · stringrequired
  • Idempotency-Keyheader · string or null

    Makes a retry safe: the same key with the same request within 24 hours gets the first answer again, and nothing is done twice. The same key with a different request is 409 idempotency-conflict. Keys are per environment and per path.

Request body · application/json

  • decision"same" | "different"required
  • decided_bystring or null

    Who did it, when your key acts for a person: their Himmel id, which the console's Developers page shows them, and Settings' People an admin. They must be in your account, with a role that could do this themselves, and the record keeps it as your key's claim. A key made for an agent names nobody but itself: leave it out. Signed in to the console, leave it out: it is you.

Responses

200 Successful response
  • recipient_idstringrequired
  • undonelist of stringrequired

    The links undone. They stay on record, marked undone.

  • recheck_jobstring or null
  • 401No API key was sent, or it is not in use (unauthorized).
  • 403The caller may not do this, and type says why: the key or role lacks the scope, the rule set is not enabled for this account, the person signed in belongs to no account, names were asked for by a caller that is not shown them, or a request acting in test tried to change the account's settings (live-only).
  • 404Nothing with that id or name exists for this account.
  • 409The request conflicts with what is stored: see detail.
  • 413The request's body is larger than the operation takes, refused before any of it is read (body-too-large): a policy's files at most 5 MB, any other body 1 MB - and an uploaded file at most 20 MB (file-too-large).
  • 422The request, or a fact in it, is not in the documented shape - a query parameter the operation does not take included, which is named (unknown_parameter) rather than ignored - or its Himmel-Env does not fit the caller. Missing facts are not errors.
  • 429More requests in a minute than the key's or the person's limit (rate-limited) - or, opening an account, more accounts in a day than the deployment opens, with Retry-After saying when to try again.
  • 500Something failed on our side. X-Request-Id identifies it in our logs.