Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions FILTERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ Pass values URL-encoded as strings. Where the server accepts arrays, comma-separ

## ProofType — `GET /v3/proof_types`

- `filter[entity_type]`
- `filter[entity_type]` — `address`, `business` or `personal`

## Region — `GET /v3/regions`

Expand Down Expand Up @@ -282,7 +282,7 @@ Pass values URL-encoded as strings. Where the server accepts arrays, comma-separ

## Export filters — `filters` of `POST /v3/exports`

Set them as a map with `Export#setFilters(Map<String, Object>)`. `from` and `to` are required for every export type, as `YYYY-MM-DD HH:MM:SS` in UTC (`from` inclusive, `to` exclusive). Where a key takes several values, pass a `List<String>`; it is sent as a JSON array. Any key that the export type does not accept is rejected.
Set them as a map with `Export#setFilters(Map<String, Object>)`. `from` and `to` are required for every export type, as `YYYY-MM-DD HH:MM:SS` in UTC (`from` inclusive, `to` exclusive). Where a key takes several values, pass a `List` (of strings, or of `ExportTrunkType` constants for `trunk_type`); it is sent as a JSON array. An enum constant of the SDK, such as `ExportServiceType.A2P`, is sent as its wire value (`a2p`); `name()` is not the wire form. The values of `service_type`, `trunk_type` and `status` are lowercase. Any key that the export type does not accept is rejected.

| Key | Export types |
|---|---|
Expand All @@ -300,11 +300,11 @@ Set them as a map with `Export#setFilters(Map<String, Object>)`. `from` and `to`
| `source_address_cont` | `sms_in`, `sms_out` |
| `destination_address_eq` | `sms_in`, `sms_out` |
| `destination_address_cont` | `sms_in`, `sms_out` |
| `status` | `sms_in` (`Success`, `Failed`, `Queued`), `sms_out` (`Routing Error`, `In Progress`, `Queued`, `Failed`, `Success`) |
| `status` | `sms_in` (`success`, `failed`, `queued`), `sms_out` (`routing_error`, `in_progress`, `queued`, `failed`, `success`) |
| `sms_id` | `sms_in`, `sms_out` |
| `sms_trunk.id` | `sms_in`, `sms_out` — one id or a list of ids |
| `trunk_type` | `sms_in` — one or a list of `ExportTrunkType` values in their wire form, e.g. `HTTP IN` |
| `service_type` | `sms_out` — `a2p` or `p2p` |
| `trunk_type` | `sms_in` — one or a list of `ExportTrunkType` values (`smpp_smsc`, `smpp_esme`, `sms_to_email`, `http_in`) |
| `service_type` | `sms_out` — an `ExportServiceType` value, `a2p` or `p2p` |
| `sender_id_verification.id` | `sms_out` — one id or a list of ids |

## Price lookups — `GET /v3/…/lookup/prices`
Expand Down
70 changes: 51 additions & 19 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ DIDWW_API_KEY=your_api_key ./gradlew runExample -PexampleClass=com.didww.example
| [`com.didww.examples.RegionsExample`](src/main/java/com/didww/examples/RegionsExample.java) | Lists regions with filters/includes and fetches a specific region. |
| [`com.didww.examples.DidGroupsExample`](src/main/java/com/didww/examples/DidGroupsExample.java) | Fetches DID groups with included SKUs, number type, demo flag and group details. |
| [`com.didww.examples.DidsExample`](src/main/java/com/didww/examples/DidsExample.java) | Updates DID routing/capacity by assigning trunk and capacity pool. |
| [`com.didww.examples.TrunksExample`](src/main/java/com/didww/examples/TrunksExample.java) | Lists trunks, creates SIP (with a point of presence) and PSTN trunks, updates and deletes them. |
| [`com.didww.examples.TrunksExample`](src/main/java/com/didww/examples/TrunksExample.java) | Lists trunks, creates SIP (with a point of presence, codecs and ICE mode) and PSTN trunks, updates and deletes them. |
| [`com.didww.examples.SharedCapacityGroupsExample`](src/main/java/com/didww/examples/SharedCapacityGroupsExample.java) | Creates a shared capacity group in a capacity pool. |
| [`com.didww.examples.OrdersExample`](src/main/java/com/didww/examples/OrdersExample.java) | Lists orders and creates/cancels a DID order using live SKU lookup. |
| [`com.didww.examples.OrdersSkuExample`](src/main/java/com/didww/examples/OrdersSkuExample.java) | Creates a DID order by SKU resolved from DID groups. |
Expand All @@ -53,7 +53,7 @@ DIDWW_API_KEY=your_api_key ./gradlew runExample -PexampleClass=com.didww.example
| [`com.didww.examples.IdentityAddressProofsExample`](src/main/java/com/didww/examples/IdentityAddressProofsExample.java) | Creates identity and address, encrypts and uploads files, attaches proofs to both. |
| [`com.didww.examples.VoiceInTrunkGroupsExample`](src/main/java/com/didww/examples/VoiceInTrunkGroupsExample.java) | CRUD for trunk groups with trunk relationships; cleans up when a step fails. |
| [`com.didww.examples.VoiceInTrunkSipRegistrationExample`](src/main/java/com/didww/examples/VoiceInTrunkSipRegistrationExample.java) | End-to-end SIP registration flow: create with `enabledSipRegistration=true`, rename, disable by setting `host`, re-enable by toggling the flag. The SDK keeps the dependent fields (`host`, `port`, `useDidInRuri`) aligned with the server's validation rules automatically. |
| [`com.didww.examples.VoiceOutTrunksExample`](src/main/java/com/didww/examples/VoiceOutTrunksExample.java) | CRUD for voice out trunks (requires account config). |
| [`com.didww.examples.VoiceOutTrunksExample`](src/main/java/com/didww/examples/VoiceOutTrunksExample.java) | CRUD for voice out trunks, with codecs and ICE mode (requires account config). |
| [`com.didww.examples.DidTrunkAssignmentExample`](src/main/java/com/didww/examples/DidTrunkAssignmentExample.java) | Demonstrates exclusive trunk/trunk group assignment on DIDs, then restores the DID's original routing. |
| [`com.didww.examples.DidReservationsExample`](src/main/java/com/didww/examples/DidReservationsExample.java) | Creates, lists, finds and deletes DID reservations. |
| [`com.didww.examples.ExportsExample`](src/main/java/com/didww/examples/ExportsExample.java) | Creates and lists CDR exports with from/to datetime range filters (UTC). |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static void main(String[] args) {
SipConfiguration sip = new SipConfiguration();
sip.setHost("sip.example.com");
sip.setPort(5060);
sip.setCodecIds(Arrays.asList(Codec.PCMU, Codec.PCMA));
sip.setCodecs(Arrays.asList(Codec.PCMU, Codec.PCMA));
sip.setTransportProtocolId(TransportProtocol.UDP);
trunk.setConfiguration(sip);
trunk.setPop(Pop.FRA); // optional; leave it unset to let DIDWW choose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@
System.out.println(" Number type: " + req.getNumberType());
System.out.println(" Identity type required: " + req.getIdentityType());
System.out.println(" Address area level: " + req.getAddressAreaLevel());
if (req.getAddressMandatoryFields() != null) {
System.out.println(" Address mandatory fields: " + String.join(", ", req.getAddressMandatoryFields()));
}
// Field names as the API spells them, such as "area" or "birth_date"; empty when none are mandatory
System.out.println(" Address mandatory fields: " + req.getAddressMandatoryFields());

Check warning on line 42 in examples/src/main/java/com/didww/examples/EmergencyRequirementsExample.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.out by a logger.

See more on https://sonarcloud.io/project/issues?id=didww_didww-api-3-java-sdk&issues=AaHFzxBhLUQgguvHBdyH&open=AaHFzxBhLUQgguvHBdyH&pullRequest=69
System.out.println(" Personal mandatory fields: " + req.getPersonalMandatoryFields());
System.out.println(" Business mandatory fields: " + req.getBusinessMandatoryFields());

Check warning on line 44 in examples/src/main/java/com/didww/examples/EmergencyRequirementsExample.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.out by a logger.

See more on https://sonarcloud.io/project/issues?id=didww_didww-api-3-java-sdk&issues=AaHFzxBhLUQgguvHBdyI&open=AaHFzxBhLUQgguvHBdyI&pullRequest=69
System.out.println(" Estimated setup time (days): " + req.getEstimateSetupTime());
if (req.getRequirementRestrictionMessage() != null) {
System.out.println(" Restriction: " + req.getRequirementRestrictionMessage());
Expand Down
17 changes: 10 additions & 7 deletions examples/src/main/java/com/didww/examples/ExportsSmsExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.didww.sdk.http.QueryParams;
import com.didww.sdk.resource.Export;
import com.didww.sdk.resource.enums.ExportStatus;
import com.didww.sdk.resource.enums.ExportServiceType;
import com.didww.sdk.resource.enums.ExportTrunkType;
import com.didww.sdk.resource.enums.ExportType;

Expand All @@ -29,10 +30,13 @@
* Filters on SMS exports (Export#setFilters):
* - "from", "to" (required): "YYYY-MM-DD HH:MM:SS" in UTC; from inclusive, to exclusive and not in the future
* - "source_address_eq" / "source_address_cont", "destination_address_eq" / "destination_address_cont",
* "status", "sms_id"
* "status" (sms_in: "success", "failed", "queued"; sms_out: "routing_error", "in_progress",
* "queued", "failed", "success"), "sms_id"
* - "sms_trunk.id": one id or a list of ids
* - sms_in only: "trunk_type", one or a list of ExportTrunkType values in their wire form
* - sms_out only: "service_type" ("a2p" or "p2p"), "sender_id_verification.id"
* - sms_in only: "trunk_type", an ExportTrunkType or a List of them (or of wire strings)
* - sms_out only: "service_type", an ExportServiceType ("a2p" or "p2p"); "sender_id_verification.id"
* The values of "service_type", "trunk_type" and "status" are lowercase; an SDK enum constant is
* sent as its wire value.
*
* Usage: DIDWW_API_KEY=xxx ./gradlew runExample -PexampleClass=com.didww.examples.ExportsSmsExample
*/
Expand All @@ -51,15 +55,14 @@
Map<String, Object> outFilters = new HashMap<>();
outFilters.put("from", from);
outFilters.put("to", to);
outFilters.put("service_type", "a2p");
outFilters.put("service_type", ExportServiceType.A2P);
create(client, ExportType.SMS_OUT, outFilters, "java-sms-out-" + suffix);

System.out.println("\n=== Creating SMS-In Export (HTTP IN and email trunks, yesterday) ===");
System.out.println("\n=== Creating SMS-In Export (HTTP IN and SMS to email trunks, yesterday) ===");

Check warning on line 61 in examples/src/main/java/com/didww/examples/ExportsSmsExample.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.out by a logger.

See more on https://sonarcloud.io/project/issues?id=didww_didww-api-3-java-sdk&issues=AaHFzw1fLUQgguvHBdyF&open=AaHFzw1fLUQgguvHBdyF&pullRequest=69
Map<String, Object> inFilters = new HashMap<>();
inFilters.put("from", from);
inFilters.put("to", to);
inFilters.put("trunk_type", Arrays.asList(
EnumWire.value(ExportTrunkType.HTTP_IN), EnumWire.value(ExportTrunkType.EMAIL)));
inFilters.put("trunk_type", Arrays.asList(ExportTrunkType.HTTP_IN, ExportTrunkType.SMS_TO_EMAIL));
create(client, ExportType.SMS_IN, inFilters, "java-sms-in-" + suffix);

System.out.println("\n=== Completed SMS-Out exports ===");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ public static void main(String[] args) throws Exception {
ProofType identityProofType = null;
ProofType addressProofType = null;
for (ProofType pt : proofTypes) {
if ("Personal".equals(pt.getEntityType()) && identityProofType == null) {
if ("personal".equals(pt.getEntityType()) && identityProofType == null) {
identityProofType = pt;
} else if ("Address".equals(pt.getEntityType()) && addressProofType == null) {
} else if ("address".equals(pt.getEntityType()) && addressProofType == null) {
addressProofType = pt;
}
if (identityProofType != null && addressProofType != null) break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private static void run(PricesService<VoiceInPrice> prices, Path path) throws IO
System.out.println("\n=== First rows ===");
try (Stream<Map<String, String>> rows = PriceFileReader.rows(path)) {
rows.limit(FIRST_ROWS).forEach(row -> System.out.println(" " + row.get("Country") + " "
+ row.get("Destination") + " " + row.get("Source type") + ": " + row.get("Rate (USD, excl. VAT)")));
+ row.get("Destination") + " " + row.get("Source type") + ": " + row.get("Rate")));
}
}

Expand Down
5 changes: 4 additions & 1 deletion examples/src/main/java/com/didww/examples/TrunksExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.didww.sdk.resource.configuration.SipConfiguration;
import com.didww.sdk.resource.enums.CliFormat;
import com.didww.sdk.resource.enums.Codec;
import com.didww.sdk.resource.enums.IceMode;
import com.didww.sdk.resource.enums.Pop;
import com.didww.sdk.resource.enums.ReroutingDisconnectCode;
import com.didww.sdk.resource.enums.TransportProtocol;
Expand Down Expand Up @@ -44,7 +45,9 @@ public static void main(String[] args) {
SipConfiguration sip = new SipConfiguration();
sip.setHost("sip.example.com");
sip.setPort(5060);
sip.setCodecIds(Arrays.asList(Codec.PCMU, Codec.PCMA));
// Codecs by name, in priority order; left out, DIDWW applies its default list
sip.setCodecs(Arrays.asList(Codec.PCMU, Codec.PCMA, Codec.TELEPHONE_EVENT));
sip.setIceMode(IceMode.ACCEPT);
sip.setTransportProtocolId(TransportProtocol.UDP);
sip.setReroutingDisconnectCodeIds(Arrays.asList(
ReroutingDisconnectCode.SIP_480_TEMPORARILY_UNAVAILABLE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private static VoiceInTrunk sipTrunk(String name, String host, Pop pop, List<Cod
SipConfiguration sip = new SipConfiguration();
sip.setHost(host);
sip.setPort(5060);
sip.setCodecIds(codecs);
sip.setCodecs(codecs);
sip.setTransportProtocolId(TransportProtocol.UDP);
trunk.setConfiguration(sip);
trunk.setPop(pop);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static void main(String[] args) {
sip.setEnabledSipRegistration(true);
sip.setUseDidInRuri(true);
sip.setCnamLookup(false);
sip.setCodecIds(Arrays.asList(Codec.PCMU, Codec.PCMA));
sip.setCodecs(Arrays.asList(Codec.PCMU, Codec.PCMA));
sip.setTransportProtocolId(TransportProtocol.UDP);

VoiceInTrunk trunk = new VoiceInTrunk();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
import com.didww.sdk.resource.authenticationmethod.IpOnlyAuthenticationMethod;
import com.didww.sdk.resource.authenticationmethod.TwilioAuthenticationMethod;
import com.didww.sdk.resource.enums.DefaultDstAction;
import com.didww.sdk.resource.enums.IceMode;
import com.didww.sdk.resource.enums.OnCliMismatchAction;
import com.didww.sdk.resource.enums.VoiceOutCodec;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
Expand Down Expand Up @@ -45,6 +48,7 @@
System.out.println(" External Reference ID: " + t.getExternalReferenceId());
System.out.println(" Emergency Enable All: " + t.getEmergencyEnableAll());
System.out.println(" RTP Timeout: " + t.getRtpTimeout());
System.out.println(" Codecs: " + t.getCodecs() + ", ICE: " + t.getIceMode());

Check warning on line 51 in examples/src/main/java/com/didww/examples/VoiceOutTrunksExample.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.out by a logger.

See more on https://sonarcloud.io/project/issues?id=didww_didww-api-3-java-sdk&issues=AaHFzw8yLUQgguvHBdyG&open=AaHFzw8yLUQgguvHBdyG&pullRequest=69
System.out.println();
}

Expand All @@ -62,6 +66,9 @@
trunk.setOnCliMismatchAction(OnCliMismatchAction.REJECT_CALL);
trunk.setExternalReferenceId("java-example-" + uniqueSuffix);
trunk.setRtpTimeout(60);
// Codecs by name, in priority order; left out, DIDWW applies its default list
trunk.setCodecs(Arrays.asList(VoiceOutCodec.OPUS, VoiceOutCodec.PCMA, VoiceOutCodec.TELEPHONE_EVENT));
trunk.setIceMode(IceMode.ACCEPT);

VoiceOutTrunk created = client.voiceOutTrunks().create(trunk).getData();
System.out.println("Created voice out trunk: " + created.getId());
Expand Down
32 changes: 20 additions & 12 deletions src/main/java/com/didww/sdk/DidwwClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.didww.sdk.internal.AtomicFile;
import com.didww.sdk.internal.Bodies;
import com.didww.sdk.internal.MultiMemberGzipInputStream;
import com.didww.sdk.internal.RetryAfter;
import com.didww.sdk.internal.Streams;
import com.didww.sdk.internal.Urls;
import com.didww.sdk.prices.PriceList;
Expand Down Expand Up @@ -226,6 +227,12 @@ public Repository<VoiceOutTrunk> voiceOutTrunks() {
return new Repository<>(httpClient, converter, baseUrl, "voice_out_trunks", VoiceOutTrunk.class, objectMapper);
}

/**
* Rotates the generated credentials of a Voice OUT trunk. Only {@code create} is supported; a trunk
* with {@code twilio} authentication is answered 404.
*
* @return the repository
*/
public Repository<VoiceOutTrunkRegenerateCredential> voiceOutTrunkRegenerateCredentials() {
return new Repository<>(httpClient, converter, baseUrl, "voice_out_trunk_regenerate_credentials", VoiceOutTrunkRegenerateCredential.class, objectMapper);
}
Expand Down Expand Up @@ -320,7 +327,9 @@ public Repository<AddressRequirementValidation> addressRequirementValidations()
* @param fingerprint encryption key fingerprint identifying the public key used
* @param description optional human-readable description for the file (may be {@code null})
* @return the encrypted file ID returned by the API
* @throws DidwwClientException if the upload fails or the response is unexpected
* @throws DidwwApiException if the API answers with an error status (see
* {@link DidwwApiException#getRetryAfter()}); a 429 is not retried
* @throws DidwwClientException if the upload fails in transport or the response is unexpected
*/
public String uploadEncryptedFile(byte[] encryptedData,
String fileName,
Expand Down Expand Up @@ -350,10 +359,7 @@ public String uploadEncryptedFile(byte[] encryptedData,

try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
byte[] errorBody = Bodies.readLimitedQuietly(response.body(), Bodies.MAX_ERROR_BODY_SIZE,
Bodies.errorBodyDeadlineMillis(httpClient));
String detail = errorBody.length > 0 ? " " + new String(errorBody, StandardCharsets.UTF_8) : "";
throw new DidwwClientException("Failed to upload encrypted file: HTTP " + response.code() + detail);
throw unexpectedStatus(response);
}

String responseBody = response.body() != null ? response.body().string() : "";
Expand All @@ -379,7 +385,8 @@ public String uploadEncryptedFile(byte[] encryptedData,
* elsewhere: a URL on any other origin raises {@link DidwwClientException}
* without a request. Only a 200 answer is written, as it arrives; redirects
* are not followed, and any other status, including 3xx and 204, raises
* {@link DidwwApiException}.
* {@link DidwwApiException} (see {@link DidwwApiException#getRetryAfter()});
* a 429 is not retried.
*
* @param url URL of the export file ({@code Export.getUrl()})
* @param destination where the file is written
Expand All @@ -396,8 +403,8 @@ public void downloadExport(String url, OutputStream destination) {
* Downloads an export file to {@code destination} unchanged (gzip-compressed
* CSV). The URL is checked and the answer handled as in
* {@link #downloadExport(String, OutputStream)}. The file is written to a
* temporary file next to {@code destination}, which replaces it only once
* the whole file has arrived.
* temporary file {@code .didww-<16 hex digits>.tmp} in the directory of
* {@code destination}, which replaces it only once the whole file has arrived.
*
* @param url URL of the export file ({@code Export.getUrl()})
* @param destination local path where the file will be written
Expand Down Expand Up @@ -429,9 +436,10 @@ public void downloadAndDecompressExport(String url, OutputStream destination) {

/**
* Downloads a gzip-compressed export file (.csv.gz) and writes the
* decompressed CSV to {@code destination} through a temporary file next to
* it, which replaces it only once the whole file has been decompressed. The
* URL is checked and the answer handled as in
* decompressed CSV to {@code destination} through a temporary file
* {@code .didww-<16 hex digits>.tmp} in its directory, which replaces it only
* once the whole file has been decompressed. The URL is checked and the
* answer handled as in
* {@link #downloadExport(String, OutputStream)}.
*
* @param url URL of the gzip-compressed export file
Expand Down Expand Up @@ -554,7 +562,7 @@ private DidwwApiException unexpectedStatus(Response response) {
error.setDetail(body.isEmpty() ? null : body);
errors = Collections.singletonList(error);
}
return new DidwwApiException(response.code(), errors);
return new DidwwApiException(response.code(), errors, RetryAfter.seconds(response));
}

private static void validateApiKey(String apiKey) {
Expand Down
Loading
Loading