❮  集成

公司 API

您的客户把权限授予您的公司,您自己的应用里就有了「开启」按钮。钥匙不经过聊天流转,每次开启都署上按下按钮的员工姓名。

适合谁:员工需要驶入封闭院落的服务——保姆与家教、保洁、上门补胎与洗车、快递与配送、上门护理与采样。客户保留现有道闸;您得到的是一个按钮,而不是在大门口给客户打电话。

工作原理

  1. 客户在您的界面里说明需要驶入。您先确认两点:道闸能用他的号码打电话开启,且他的手机保持在线。若道闸装了 Entrixy 控制器,则完全不需要手机。
  2. 您创建一次请求并展示它的二维码。客户扫码、安装应用、添加道闸并授予权限——您的公司已经在列表里等着。
  3. 您通过 API 开启并注明是哪位员工。客户在日志里看到带该姓名的开启记录,随时一点即可收回权限。
钥匙并不以链接形式存在。公司拿到的是指向它的引用而非钥匙本身,没有可复制或转发的东西;钥匙绑定在您的账户上,只能通过本 API 使用。无法把权限再转交给员工——这是刻意为之,也正因如此客户才放心授予。

认证

There are two ways in, and the difference is what the client sees.

Signature bound to your domain

You publish a public key on your own domain and sign every call with the private one. We take the key from the domain itself, so the right to call and the right to be shown under that domain are the same thing: the file disappears, the domain moves or changes hands — the calls stop at once. There is no one-off "verified" tick to outlive reality.

https://<your domain>/.well-known/entrixy.json

{ "key": "<Ed25519 public key, 32 bytes, base64>" }

密钥对在你这一侧生成,私钥我们永远看不到。两条命令就够:

openssl genpkey -algorithm ed25519 -out entrixy-private.pem
openssl pkey -in entrixy-private.pem -pubout -outform DER | tail -c 32 | base64

第二条打印写进文件的字符串。第一条把私钥留在你这里:它给每次调用签名,没有任何需要外发的地方。

Then each request carries four headers:

X-Entrixy-Org:   your-domain.com
X-Entrixy-Ts:    1750000000          // unix seconds, ±300 s
X-Entrixy-Nonce: 0011…ff             // 32 lowercase hex characters, used once
X-Entrixy-Sig:   <base64>            // Ed25519 over the base string

base = "<domain>.<ts>.<nonce>." + sha256(raw request body)

A repeated nonce is refused, and so is a body that does not match the signature. This is the mode where the client sees your domain and your logo.

A domain written in national characters goes into the header and into the base string in its punycode form — xn--… — the same form it takes in the address of the key file. The client is still shown the readable spelling.

Signing in PHP, for example. openssl leaves a PEM file with a 32-byte seed, while sodium signs with the 64-byte secret key, so the first three lines derive one from the other:

$pem   = file_get_contents('entrixy-private.pem');
$der   = base64_decode(preg_replace('/-----[^-]+-----|\s+/', '', $pem));
$secretKey = sodium_crypto_sign_secretkey(sodium_crypto_sign_seed_keypair(substr($der, -32)));

$body  = json_encode($payload, JSON_UNESCAPED_UNICODE);
$ts    = time();
$nonce = bin2hex(random_bytes(16));      // 32 lowercase hex characters
$base  = "$domain.$ts.$nonce." . hash('sha256', $body);
$sig   = base64_encode(sodium_crypto_sign_detached($base, $secretKey));

Send the body exactly as it was signed, byte for byte, together with the four headers.

A call is refused before any action when the authentication does not pass:

响应Meaning
401 authunknown company or a wrong secret
401 stale_timestampthe time in the header differs from ours by more than 300 seconds
401 bad_noncethe nonce is not 32 hex characters
401 replaythis nonce has already been used
401 bad_signaturethe signature does not match the body
401 domain_key_unavailablethe key file could not be fetched from your domain
401 sign_requiredyour domain already has a key, so a secret is no longer accepted
403 org_blockedthe company is blocked

A secret, if you have no domain

Register, issue a secret in the cabinet and put it in the body together with your company number. A secret is enough to create requests and to read their state and contents: a=request, a=status and a=objects. The client then sees only your name with a note that the domain is not confirmed — no logo, no domain.

Opening needs the domain signature. The client’s phone checks every opening of a company with the key from its domain, so a=open with a secret alone answers 401 sign_required. As soon as the key appears on your domain the secret stops working for every call: otherwise a leaked secret would speak in the name of a confirmed company.

POST https://entrixy.com/api/company.php?a=<action>
Content-Type: application/json

{ "org_id": 17, "secret": "…", … }

The examples below show the secret in the body for brevity. With the signature the same body goes without org_id and secret, and the four headers are added.

创建请求

POST /api/company.php?a=request
{ "org_id": 17, "secret": "…", "ref": "order-517", "ttl_hours": 72 }

→ { "code": "rrGzNweV31rkHV6Q",
    "url":  "https://entrixy.com/c/rrGzNweV31rkHV6Q",
    "expires_in_hours": 72 }

ref 是您自己的订单号,会在状态里回传。请把 url 放进二维码展示给客户。

请求状态

POST /api/company.php?a=status
{ "org_id": 17, "secret": "…", "code": "rrGzNweV31rkHV6Q" }

→ { "state": "issued", "key_ref": 4821, "ref": "order-517",
    "claimed_at": "2026-09-09 12:20:11", "issued_at": "2026-09-09 12:24:03" }
状态Meaning
new二维码已创建,客户尚未到来
claimed客户已打开应用——正在进行中
issued权限已授予; key_ref 用它来开启
revoked客户收回了权限
已过期客户到来之前请求已过期

钥匙能开什么

The answer to a=status already carries the contents of the key, and the same list comes from a=objects — by key_ref or by the request code.

POST /api/company.php?a=objects
{ "org_id": 17, "secret": "…", "key_ref": 96 }

{
  "key_ref": 96,
  "numbers": [
    { "number_id": 83, "name": "Шлагбаум во двор", "type": "call", "openable": true },
    { "number_id": 84, "name": "Ворота на парковку", "type": "call", "openable": true }
  ]
}

number_id is what you pass to a=open; it belongs to the object and does not change when the client renames it. name is the name the client gave the object and agreed to show you — the client's own names are encrypted and we do not see them, so this one arrives only because the client sent it along with the access. Until an older app is updated the field comes as null: show the visit address instead. openable is false for an object that only its holder can open — a call from the company will be refused.

When a key covers a single object, number_id 在 a=open may be omitted.

开启

POST /api/company.php?a=open
Content-Type: application/json
X-Entrixy-Org:   your-domain.com
X-Entrixy-Ts:    1750000000
X-Entrixy-Nonce: 9f86d081884c7d659a2feaa0c55ad015
X-Entrixy-Sig:   <base64>

{ "key_ref": 4821, "number_id": 93, "actor": "Ivan P., shift 12" }

→ { "ok": 1 }

actor 是必填项:客户在日志里看到的就是它。凡是能在您那边认出员工的都行——姓名、工号、班次。不填则调用被拒。

响应Meaning
401 sign_requiredthe call is not signed with the domain key: a secret is not enough to open
400 bad_inputkey_ref is missing
400 number_id_requiredthe key covers several objects and the object was not named
400 actor_required未注明员工
400 unsupported_objectthis object cannot be opened by a company: its openable is false
403 revokedthe client took the access back, the key is not yours, or the object is not in it
403 expired钥匙的有效期已结束
429 rate_limit同一把钥匙每分钟开启次数过多

The authentication answers from the table above apply here too.

您在应用中的名片

注册公司时把名称和方形标志发给我们。客户会在扫码打开的页面、授权确认页以及日志的每一行旁看到它们——一眼就知道放谁进来。标志由我们自己提供,不从其他服务器加载图片。

当客户收回权限时

The client removes the company in their app — the key disappears at once. a=open answers revoked, a=objects the same, and a=status shows the request as revoked with an empty list of objects. Nothing else has to be done on your side: a revoked access never comes back, a new visit needs a new request.

If you saved a callback address, we also ping it on the events — claimed, issued, revoked:

POST <your callback address>
{ "event": "revoked", "org_id": 5, "code": "eAc9SZ8mmeegD_GJ", "key_ref": 96 }

The address must start with https. The ping waits 3 seconds for an answer and does not follow redirects.

The ping is a nudge, not the source of truth: it carries no object list and is sent once, without retries. Take the details from a=status — that call is signed and answers the same thing whether the ping arrived or not.

快速上手

在你的账号里注册公司——一张表单,立刻生效。域名、密钥、回调地址、标志和申请列表都在公司后台。

公司后台