# Quickstart

> Send your first notification in about five minutes, from a token to a tracked delivery.

You need an OAuth application (client id and secret) from your Nofy
operator. Everything else below is self-service.

!!! info "Base URL"
    Every request goes to `https://nofy.encipher.co.tz`. The original host,
    `https://ens.encipher.co.tz`, serves the same API for existing
    integrations.

### Get a token
Exchange your client credentials for a Bearer token:

```bash
curl -X POST https://nofy.encipher.co.tz/v1/oauth2/token/ \
  -d grant_type=client_credentials \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET
```

```json
{
  "access_token": "abc123",
  "expires_in": 36000,
  "token_type": "Bearer",
  "scope": "read write"
}
```

Pass it as `Authorization: Bearer abc123` on every call. Tokens carry no
user: the API authorizes the *client*, and every send is attributed to it.
### Provision a tenant
A tenant is one of your customers. Sends to unknown tenants are rejected,
so register each one first. Provisioning again is safe and returns the
existing tenant:

```bash
curl -X POST https://nofy.encipher.co.tz/v2/tenants \
  -H "Authorization: Bearer abc123" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "company-uuid-1", "name": "Acme Fuel"}'
```
### Send a message
One handoff with an `Idempotency-Key` header, so a retried request never
sends twice. Python has an SDK; every other language calls the same HTTP
API:

```python Python
from ens_sdk import ENS

# ENS_SERVER_URL, ENS_CLIENT_ID, ENS_CLIENT_SECRET, ENS_TENANT
ens = ENS()
ens.send_sms("+255700000000", "Shift closed at 18:00.")
```

```javascript Node.js
// Mint a token first: POST /v1/oauth2/token/ (grant_type=client_credentials)
const res = await fetch("https://nofy.encipher.co.tz/v2/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ENS_TOKEN}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "notif-0001",
  },
  body: JSON.stringify({
    tenant: "company-uuid-1",
    event_type: "shift.closed",
    messages: [{
      channel: "sms",
      recipients: ["255700000000"],
      content: { kind: "rendered", body: "Shift closed at 18:00." },
    }],
  }),
});
if (res.status !== 202) throw new Error(`ENS ${res.status}`);
console.log(await res.json());
```

```php PHP
<?php
// Mint a token first: POST /v1/oauth2/token/ (grant_type=client_credentials)
$ch = curl_init("https://nofy.encipher.co.tz/v2/messages");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("ENS_TOKEN"),
    "Content-Type: application/json",
    "Idempotency-Key: notif-0001",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "tenant" => "company-uuid-1",
    "event_type" => "shift.closed",
    "messages" => [[
      "channel" => "sms",
      "recipients" => ["255700000000"],
      "content" => ["kind" => "rendered", "body" => "Shift closed at 18:00."],
    ]],
  ]),
]);
$body = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 202) {
  throw new Exception("ENS send failed");
}
```

```go Go
// Mint a token first: POST /v1/oauth2/token/ (grant_type=client_credentials)
body, _ := json.Marshal(map[string]any{
    "tenant":     "company-uuid-1",
    "event_type": "shift.closed",
    "messages": []any{map[string]any{
        "channel":    "sms",
        "recipients": []string{"255700000000"},
        "content":    map[string]string{"kind": "rendered", "body": "Shift closed at 18:00."},
    }},
})
req, _ := http.NewRequest("POST", "https://nofy.encipher.co.tz/v2/messages", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("ENS_TOKEN"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "notif-0001")
res, err := http.DefaultClient.Do(req)
if err != nil || res.StatusCode != 202 {
    log.Fatal("ENS send failed")
}
```

```rust Rust
// Cargo.toml: reqwest { version = "0.12", features = ["json", "rustls-tls"] }, tokio, serde_json
// Mint a token first: POST /v1/oauth2/token/ (grant_type=client_credentials)
let res = reqwest::Client::new()
    .post("https://nofy.encipher.co.tz/v2/messages")
    .bearer_auth(std::env::var("ENS_TOKEN")?)
    .header("Idempotency-Key", "notif-0001")
    .json(&serde_json::json!({
        "tenant": "company-uuid-1",
        "event_type": "shift.closed",
        "messages": [{
            "channel": "sms",
            "recipients": ["255700000000"],
            "content": {"kind": "rendered", "body": "Shift closed at 18:00."}
        }]
    }))
    .send()
    .await?;
if res.status() != 202 {
    anyhow::bail!("ENS send failed: {}", res.status());
}
```

```bash cURL
curl -X POST https://nofy.encipher.co.tz/v2/messages \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: notif-0001" \
  -d '{"tenant": "company-uuid-1", "event_type": "shift.closed",
       "messages": [{"channel": "sms", "recipients": ["255700000000"],
       "content": {"kind": "rendered", "body": "Shift closed at 18:00."}}]}' 
```


A `202` means *accepted and queued*, not delivered. The response lists one
delivery per recipient, each starting as `pending`:

```json
{
  "reference": "shift-8841",
  "message_id": "vA51xTo874Y8",
  "deliveries": [
    {
      "id": "dL91...",
      "channel": "sms",
      "recipient": "255700000000",
      "status": "pending"
    }
  ]
}
```
### Track the delivery
Pull your deliveries, filtered by your own reference:

```bash
curl "https://nofy.encipher.co.tz/v2/deliveries?reference=shift-8841" \
  -H "Authorization: Bearer abc123"
```

Or pass a `callback_url` on the send and receive a signed post as each
delivery resolves.

## Next steps

### Sending on v2
Email with inline images, Push to your user ids and WhatsApp templates.
### Callbacks
Verify signed status posts and reconcile after downtime.
