// FireFlo SMS gateway: gRPC API, version 1.
// Frozen contract. Field numbers are never reused or renumbered; additions only.
syntax = "proto3";
package fireflo.v1;
option java_multiple_files = true;
option java_outer_classname = "FireFloProto";
option go_package = "fireflo/v1;fireflov1";
option csharp_namespace = "FireFlo.V1";
import "google/protobuf/timestamp.proto";
// ---------------------------------------------------------------- Auth
// Call Login once per channel; send the token on every other call as
// authorization: Bearer <session_token>
service AuthService {
rpc Login(LoginRequest) returns (LoginResponse);
rpc Logout(LogoutRequest) returns (LogoutResponse);
// A platform session (from 0.15.10): for a platform that provisions accounts with
// the gateway's management token and sends on their behalf. No password; the call
// is signed with the management token instead (see PlatformLoginRequest). The
// token returned is used exactly as a Login token.
rpc PlatformLogin(PlatformLoginRequest) returns (LoginResponse);
}
message LoginRequest {
string system_id = 1; // the HTTP login (same as REST Basic username)
string password = 2;
}
message LoginResponse {
string session_token = 1;
google.protobuf.Timestamp expires_at = 2; // absolute cap; idle expiry also applies
int64 idle_timeout_seconds = 3;
}
message LogoutRequest {}
message LogoutResponse {}
// Signed, not passworded. The call carries three metadata entries:
// x-fireflo-timestamp Unix time in seconds, within 60 s of the gateway's clock
// x-fireflo-nonce 1..128 characters, unique per call; a nonce is accepted once
// x-fireflo-signature hex HMAC-SHA256, keyed by the management token, of these
// five lines joined by "\n", with no trailing newline:
// POST
// /fireflo.v1.AuthService/PlatformLogin
// <the timestamp, as sent>
// <the nonce, as sent>
// <lowercase hex SHA-256 of this message's serialized bytes>
// The session acts for the accounts whose ref (the marker the platform provisioned
// them with) starts with this ref, and for no other.
message PlatformLoginRequest {
optional string ref = 1; // required: a prefix of the accounts' refs, ending in "/"
}
// ---------------------------------------------------------------- Send
service SendService {
// One message. Mirrors POST /secure/send.
rpc Send(SendRequest) returns (SendResponse);
// Many messages on one stream. Results arrive in any order, one per request,
// matched by correlation_id. A refusal is a result, not a stream error.
rpc SendStream(stream SendStreamRequest) returns (stream SendStreamResult);
}
// Field for field the body of POST /secure/send. Unset optional fields take the
// same defaults as an absent JSON key.
message SendRequest {
string to = 1;
optional string from = 2;
// exactly one of content / hex_content
optional string content = 3;
optional string hex_content = 4;
optional int32 coding = 5; // 0 GSM-7 default, 8 UCS-2, ... as REST
optional int32 priority = 6; // 0..3
optional int32 validity_period = 7; // minutes
optional string sdt = 8; // scheduled delivery time, REST format
optional bool dlr = 9; // request receipts (to the feed unless dlr_url set)
optional int32 dlr_level = 10; // 1 progress, 2 final, 3 both
map<string, TlvValue> custom_tlvs = 11; // key "0xTAG" or "0xTAG:Type", as REST
optional string client_ref = 12; // 1..64 printable chars; idempotency key
optional string product = 13;
optional string dlr_url = 14; // if set, receipts go to this webhook and NOT the feed
reserved 15, 16; // dlr_method, tags (REST/Jasmin compatibility; not carried)
// Platform sessions only, and required on them: the account to send as. The
// message runs under that account's enabled HTTP login, with its prices, limits
// and receipt feed. Refused on a Login session.
optional string account = 17;
}
message TlvValue {
oneof value {
string string_value = 1;
int64 int_value = 2;
uint64 uint_value = 3; // up to 2^64-1 exactly, the same range REST takes as a JSON integer
}
}
message SendResponse {
string message_id = 1;
bool duplicate = 2; // client_ref seen before: message_id is the original's, nothing charged
optional string price = 3; // decimal string, 4 places, whole message (REST "price")
optional string currency = 4;
optional int32 segments = 5;
bool unrated = 6;
}
message SendStreamRequest {
string correlation_id = 1; // client-chosen, 1..64 chars, echoed back
SendRequest request = 2;
}
message SendStreamResult {
string correlation_id = 1;
int32 code = 2; // google.rpc.Code value the unary Send would have returned (0 = OK)
SendResponse response = 3; // set when code == 0
ErrorDetail error = 4; // set when code != 0
}
// Packed into google.rpc.Status.details on every non-OK unary status,
// and inline in SendStreamResult.
message ErrorDetail {
int32 http_status = 1; // what POST /secure/send would have answered
string code = 2; // REST error code, e.g. RATE_LIMITED, OUTCOME_UNKNOWN, SHUTTING_DOWN
string message = 3; // REST error message, verbatim
optional int32 retry_after_seconds = 4;
optional string message_id = 5; // OUTCOME_UNKNOWN only
optional string field = 6; // the offending request field for INVALID_ARGUMENT
}
// ---------------------------------------------------------------- Receipts
// One durable queue per HTTP login. Subscribe and Pull draw from the same queue.
// Delivery is at least once: a receipt is removed only by Ack. An unacked receipt
// is redelivered after its lease (default 120 s) or as soon as its stream closes.
// On a platform session the three calls read the receipts of every account the
// platform owns; account_id and system_id say whose each one is. An ack token is
// accepted only by the kind of session (login or platform) it was delivered to.
service ReceiptService {
rpc Subscribe(SubscribeRequest) returns (stream Receipt);
rpc Pull(PullRequest) returns (PullResponse);
rpc Ack(AckRequest) returns (AckResponse);
}
message SubscribeRequest {
optional int32 window = 1; // max unacked receipts in flight on this stream; server caps
}
message PullRequest {
int32 max = 1; // 1..1000 (server cap)
optional int32 wait_seconds = 2; // long-poll up to 20 s when empty; 0 = return at once
}
message PullResponse {
repeated Receipt receipts = 1;
}
message AckRequest {
repeated string ack_tokens = 1; // up to 1000
}
message AckResponse {
int32 acked = 1; // removed now
int32 already_gone = 2; // unknown, already acked or expired: not an error
}
// The receipt webhook JSON (docs.fireflo.au/reference/api/dlr-webhook), one field per key, plus delivery fields.
message Receipt {
string id = 1; // message_id from SendResponse
string stat = 2; // DELIVRD, UNDELIV, EXPIRED, REJECTD, UNKNOWN, ACCEPTD, ENROUTE...
int32 level = 3; // 1 progress, 2 final
optional string sub_date = 4;
optional string done_date = 5;
optional string to = 6;
optional string from = 7;
optional string operator_msg_id = 8;
optional string account_id = 9;
optional string system_id = 10;
optional int32 parts = 11;
optional string vendor = 12; // absent unless the operator shares it
optional string err = 13;
string ack_token = 14;
int32 delivery_count = 15; // 1 on first delivery; >1 means a redelivery
}