# Submit a ready registration for carrier review (or resubmit after a rejection)

Source: https://jointextkit.com/docs/api/registrations/submit
Index: https://jointextkit.com/llms.txt

`POST /v1/registrations/{id}/submit`

The body must carry `fees_acknowledged: true`: your acceptance of the registration fee terms (Terms §4.3). Carrier registration fees (brand, vetting, the monthly campaign fee) are separate from the per-message rate and billed at twice what the registry or carrier charges us; a campaign is billed for a minimum of 3 months; the fees are non-refundable, including if the registration is rejected. The acknowledgment is recorded on the filed copy with its wording, the time and your IP; without it the request is refused with 400 invalid_request (param fees_acknowledged) and nothing is filed. The first submit for a business also carries the signed business attestation; without one on file the submit answers 400 attestation_required (param attestation). The submission is then held while our team verifies the business (guidance.next_actor delivered), and filed once it is verified. Later submits for the same business details need no attestation. A business we could not verify returns the registration as rejected with reason code business_unverified; the next submit is signed again.

Send your API key as a bearer token: `Authorization: Bearer ssms_sk_...`. Test keys run this endpoint against the [sandbox](/docs/sandbox); see [Authentication](/docs/authentication) for key modes and scopes.

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes |  |

## Request body

JSON (`Content-Type: application/json`).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `fees_acknowledged` | `true` | Yes | Must be true. Confirms the registration fee terms above. |
| `attestation` | BusinessAttestation | No | Signed by someone authorized to act for the business, once per business: that they are authorized, that the details match the business's IRS records, and that the business will text only people who agreed to it and will follow the Messaging Policy. Kept as signed, with the exact wording, who sent it (the API key), the IP address, the user agent and the date of our legal documents. |

## Responses

| Status | Meaning | Body |
| --- | --- | --- |
| 200 | Registration, status submitted | `Registration` |
| 400 | invalid_request: fees_acknowledged missing or not true (param fees_acknowledged), or a missing business detail or attestation field; attestation_required: the business has no signature on file for these details | `Error` |
| 401 | Missing, malformed, or revoked API key | `Error` |
| 402 | Billable live calls only: the prepaid credit balance cannot cover the call (insufficient_credits); nothing was done or charged | `Error` |
| 409 | invalid_state: not ready (message says what to do) | `Error` |
| 429 | Rate limit or quota exceeded | `Error` |

### 200: `Registration` fields

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Example: `reg_a1B2c3D4e5F6`. |
| `object` | `registration` |  |
| `status` | `draft`, `checks_failed`, `ready`, `submitted`, `in_review`, `approved`, `rejected` |  |
| `business_name` | string |  |
| `website` | string |  |
| `use_case` | string |  |
| `opt_in_url` | string | Nullable. |
| `privacy_url` | string | Nullable. |
| `terms_url` | string | Nullable. |
| `support_email` | string | Nullable. |
| `brand` | object | The business details as saved (the brand object of RegistrationInput, including the authorized representative: contact_name, contact_title, contact_email, contact_phone). |
| `sample_messages` | array of string | The examples as they stand: yours, or starter drafts (see samples_source). These are what a submission files. |
| `samples_source` | `starter`, `customer` | starter: our drafts, not yet edited or confirmed; the registration cannot be ready. customer: yours. |
| `description` | string | Your own description; null when the generated one (generated.campaign.description) is filed Nullable. |
| `opt_in_flow` | string | Your own account of how people opt in; null when the standard sentence is filed Nullable. |
| `stop_reply` | string | Your wording of the opt-out confirmation; null when the standard wording (generated.stop_reply) is filed and sent Nullable. |
| `help_reply` | string | Your wording of the HELP reply; null when the standard wording (generated.help_reply) is filed and sent Nullable. |
| `includes_links` | boolean |  |
| `includes_phone_numbers` | boolean |  |
| `age_gated` | boolean |  |
| `direct_lending` | boolean |  |
| `marketing_paused` | object | Set while marketing from this registration is paused because more than 4% of recipients opted out in 24 hours; our team resumes it. Transactional messages are not paused. Nullable. |
| `marketing_paused.paused_at` | string (date-time) |  |
| `marketing_paused.opt_out_rate` | number | Opt-outs divided by messages sent, over the 24 hours that tripped it |
| `marketing_paused.sent` | integer |  |
| `marketing_paused.opt_outs` | integer |  |
| `sample_check` | object | The example-message check. Fixed rules. Recomputed on every read. |
| `sample_check.passed` | integer |  |
| `sample_check.failed` | integer |  |
| `sample_check.warnings` | integer |  |
| `sample_check.ok` | boolean |  |
| `sample_check.findings` | array of SampleFinding |  |
| `guidance` | object | The five answers every state gives |
| `guidance.what_happened` | string |  |
| `guidance.why` | string |  |
| `guidance.next_actor` | `you`, `delivered`, `carriers`, `nobody` |  |
| `guidance.next_step` | string |  |
| `guidance.after` | string |  |
| `check` | object | Nullable. |
| `check.checked_at` | string (date-time) |  |
| `check.passed` | integer |  |
| `check.failed` | integer |  |
| `check.warnings` | integer |  |
| `check.urls` | object |  |
| `check.findings` | array of Finding |  |
| `rejection` | object | Nullable. |
| `rejection.code` | string |  |
| `rejection.title` | string |  |
| `rejection.explanation` | string |  |
| `rejection.fix` | string |  |
| `rejection.note` | string | Nullable. |
| `rejection.related_findings` | array of string |  |
| `rejection.rejected_at` | string (date-time) |  |
| `generated` | object | Deterministic compliant copy and the registry draft |
| `generated.opt_in_cta` | string |  |
| `generated.privacy_clause` | string |  |
| `generated.sms_terms` | string |  |
| `generated.sample_messages` | array of string | Starter drafts from templates. What is filed is the top-level sample_messages (also in generated.campaign.sample_messages). At most 5 items. At least 2 items. |
| `generated.help_reply` | string |  |
| `generated.stop_reply` | string |  |
| `generated.opt_in_confirmation` | string |  |
| `generated.brand` | object |  |
| `generated.brand.missing` | array of string |  |
| `generated.campaign` | object |  |
| `generated.campaign.use_case` | string |  |
| `generated.campaign.description` | string |  |
| `generated.campaign.message_flow` | string |  |
| `generated.campaign.sample_messages` | array of string |  |
| `generated.campaign.opt_in_keywords` | array of string |  |
| `generated.campaign.opt_in_message` | string |  |
| `generated.campaign.opt_out_keywords` | array of string |  |
| `generated.campaign.opt_out_message` | string |  |
| `generated.campaign.help_keywords` | array of string |  |
| `generated.campaign.help_message` | string |  |
| `generated.campaign.embedded_link` | boolean |  |
| `generated.campaign.embedded_phone` | boolean |  |
| `generated.campaign.number_pooling` | boolean |  |
| `generated.campaign.direct_lending` | boolean |  |
| `generated.campaign.age_gated` | boolean |  |
| `generated.campaign.affiliate_marketing` | boolean |  |
| `generated.campaign.privacy_policy_url` | string |  |
| `generated.campaign.terms_url` | string |  |
| `submissions` | integer |  |
| `history` | array of object |  |
| `history[].at` | string (date-time) |  |
| `history[].status` | string |  |
| `history[].actor` | `customer`, `delivered`, `system` |  |
| `history[].label` | string |  |
| `history[].reason_code` | string |  |
| `history[].note` | string |  |
| `created_at` | string (date-time) |  |
| `updated_at` | string (date-time) |  |

## Errors

| Status | When |
| --- | --- |
| 400 | invalid_request: fees_acknowledged missing or not true (param fees_acknowledged), or a missing business detail or attestation field; attestation_required: the business has no signature on file for these details |
| 401 | Missing, malformed, or revoked API key |
| 402 | Billable live calls only: the prepaid credit balance cannot cover the call (insufficient_credits); nothing was done or charged |
| 409 | invalid_state: not ready (message says what to do) |
| 429 | Rate limit or quota exceeded |

Every error has the same JSON shape, and `request_id` matches the `X-Request-Id` response header. [Errors](/docs/errors) lists every code and what to do about it.

```json
{
  "error": {
    "code": "invalid_request",
    "message": "What went wrong, in plain words.",
    "param": "the_field",
    "request_id": "req_a1B2c3D4e5F6g7H8"
  }
}
```

## Examples

### Node.js

The Node.js SDK does not wrap this endpoint yet; call it over HTTP.

```javascript
const res = await fetch('https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TEXTKIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "fees_acknowledged": true
  }),
});

if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();
```

### PHP

```php
<?php

$ch = curl_init('https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('TEXTKIT_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'fees_acknowledged' => true,
    ]),
]);

$data = json_decode(curl_exec($ch), true);
```

### Python

The Python SDK does not wrap this endpoint yet; call it over HTTP.

```python
import json, os, urllib.request

req = urllib.request.Request(
    "https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit",
    method="POST",
    headers={
        "Authorization": f"Bearer {os.environ['TEXTKIT_API_KEY']}",
        "Content-Type": "application/json",
    },
    data=json.dumps({
        "fees_acknowledged": True
    }).encode(),
)
with urllib.request.urlopen(req) as res:
    data = json.load(res)
```

### Ruby

```ruby
require "net/http"
require "json"

uri = URI("https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV["TEXTKIT_API_KEY"]}"
req["Content-Type"] = "application/json"
req.body = {
  "fees_acknowledged": true
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
```

### Go

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
	  "fees_acknowledged": true
	}`)
	req, _ := http.NewRequest("POST", "https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTKIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	data, _ := io.ReadAll(res.Body)
	fmt.Println(res.Status, string(data))
}
```

### Rust

```rust
// cargo add reqwest --features blocking,json
// cargo add serde_json
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("TEXTKIT_API_KEY")?;
    let res = reqwest::blocking::Client::new()
        .post("https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit")
        .bearer_auth(key)
        .json(&json!({
          "fees_acknowledged": true
        }))
        .send()?
        .error_for_status()?;
    let data: serde_json::Value = res.json()?;
    println!("{data}");
    Ok(())
}
```

### Java

```java
import java.net.URI;
import java.net.http.*;

public class Main {
    public static void main(String[] args) throws Exception {
        var request = HttpRequest.newBuilder(URI.create("https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit"))
            .header("Authorization", "Bearer " + System.getenv("TEXTKIT_API_KEY"))
            .header("Content-Type", "application/json")
            .method("POST", HttpRequest.BodyPublishers.ofString("""
                {
                  "fees_acknowledged": true
                }
                """))
            .build();

        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode() + " " + response.body());
    }
}
```

### .NET

```csharp
using System.Net.Http.Headers;
using System.Text;

var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("TEXTKIT_API_KEY"));

var body = new StringContent("""
    {
      "fees_acknowledged": true
    }
    """, Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit", body);
res.EnsureSuccessStatusCode();

var data = await res.Content.ReadAsStringAsync();
```

### cURL

```bash
curl -X POST "https://api.jointextkit.com/v1/registrations/reg_a1B2c3D4e5F6/submit" \
  -H "Authorization: Bearer $TEXTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "fees_acknowledged": true
}'
```

## Related

- Guide: [Compliance & registration](/docs/compliance)
- [All registrations endpoints](/docs/api#registrations)
- [API reference](/docs/api)
